diff --git a/.circleci/config.yml b/.circleci/config.yml
new file mode 100644
index 000000000..b5c43d017
--- /dev/null
+++ b/.circleci/config.yml
@@ -0,0 +1,63 @@
+version: 2
+jobs:
+ build:
+ working_directory: /root/SRB2
+ docker:
+ - image: debian:jessie
+ environment:
+ CC: ccache gcc -m32
+ PKG_CONFIG_LIBDIR: /usr/lib/i386-linux-gnu/pkgconfig
+ LIBGME_CFLAGS: -I/usr/include
+ LIBGME_LDFLAGS: -lgme
+ CCACHE_COMPRESS: true
+ WFLAGS: -Wno-unsuffixed-float-constants
+ GCC49: true
+ #- image: ubuntu:trusty
+ # environment:
+ # CC: ccache gcc -m32
+ # PKG_CONFIG_LIBDIR: /usr/lib/i386-linux-gnu/pkgconfig
+ # LIBGME_CFLAGS: -I/usr/include
+ # LIBGME_LDFLAGS: -lgme
+ # CCACHE_COMPRESS: true
+ # WFLAGS: -Wno-unsuffixed-float-constants
+ # GCC48: true
+ steps:
+ - run:
+ name: Add i386 arch
+ command: dpkg --add-architecture i386
+ - run:
+ name: Update APT listing
+ command: apt-get -qq update
+ - run:
+ name: Support S3 upload
+ command: apt-get -qq -y install ca-certificates
+ - restore_cache:
+ keys:
+ - v1-SRB2-APT
+ - run:
+ name: Install SDK
+ command: apt-get -qq -y install git build-essential nasm libpng12-dev:i386 libsdl2-mixer-dev:i386 libgme-dev:i386 gettext ccache wget gcc-multilib upx
+ - save_cache:
+ key: v1-SRB2-APT
+ paths:
+ - /var/cache/apt/archives
+ - checkout
+ - run:
+ name: Clean build
+ command: make -C src LINUX=1 clean
+ - restore_cache:
+ keys:
+ - v1-SRB2-{{ .Branch }}-{{ checksum "objs/Linux/SDL/Release/depend.dep" }}
+ - run:
+ name: Compile
+ command: make -C src LINUX=1 ERRORMODE=1 -k
+ - store_artifacts:
+ path: /root/SRB2/bin/Linux/Release/
+ destination: bin
+ - save_cache:
+ key: v1-SRB2-{{ .Branch }}-{{ checksum "objs/Linux/SDL/Release/depend.dep" }}
+ paths:
+ - /root/.ccache
+
+
+
diff --git a/CMakeLists.txt b/CMakeLists.txt
index cb93d22f0..f9364fdd2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.0)
project(SRB2
- VERSION 2.1.14
+ VERSION 2.1.19
LANGUAGES C)
if(${PROJECT_SOURCE_DIR} MATCHES ${PROJECT_BINARY_DIR})
diff --git a/README.md b/README.md
index eb06156b4..d16071454 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,7 @@
[](https://ci.appveyor.com/project/STJr/srb2)
[](https://travis-ci.org/STJr/SRB2)
+[](https://circleci.com/gh/STJr/SRB2/tree/master)
[Sonic Robo Blast 2](https://srb2.org/) is a 3D Sonic the Hedgehog fangame based on a modified version of [Doom Legacy](http://doomlegacy.sourceforge.net/).
diff --git a/SRB2.cbp b/SRB2.cbp
index 99a712264..88dc400fe 100644
--- a/SRB2.cbp
+++ b/SRB2.cbp
@@ -14,7 +14,7 @@ If you are compiling for Windows, use Mingw targets
Interface Defines:
_WINDOWS for DirectX Interface
SDL for SDL Interface
-HAVE_MIXER for SDL_Mixer
+HAVE_MIXER for SDL2_mixer
HAVE_PNG for PNG support (for APNG support. compile libs/libpng-src)
HWRENDER for hardware render support
@@ -31,7 +31,7 @@ HW3SOUND for 3D hardware sound support
-
+
@@ -41,8 +41,8 @@ HW3SOUND for 3D hardware sound support
-
-
+
+
@@ -54,7 +54,7 @@ HW3SOUND for 3D hardware sound support
-
+
@@ -62,8 +62,8 @@ HW3SOUND for 3D hardware sound support
-
-
+
+
@@ -74,7 +74,7 @@ HW3SOUND for 3D hardware sound support
-
+
@@ -89,9 +89,9 @@ HW3SOUND for 3D hardware sound support
-
+
-
+
@@ -104,7 +104,7 @@ HW3SOUND for 3D hardware sound support
-
+
@@ -117,9 +117,9 @@ HW3SOUND for 3D hardware sound support
-
+
-
+
@@ -152,6 +152,8 @@ HW3SOUND for 3D hardware sound support
+
+
@@ -167,6 +169,8 @@ HW3SOUND for 3D hardware sound support
+
+
@@ -198,6 +202,8 @@ HW3SOUND for 3D hardware sound support
+
+
@@ -213,6 +219,8 @@ HW3SOUND for 3D hardware sound support
+
+
@@ -567,7 +575,7 @@ HW3SOUND for 3D hardware sound support
-
+
@@ -606,7 +614,7 @@ HW3SOUND for 3D hardware sound support
-
+
@@ -884,385 +892,90 @@ HW3SOUND for 3D hardware sound support
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -1270,478 +983,62 @@ HW3SOUND for 3D hardware sound support
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
@@ -1772,187 +1069,28 @@ HW3SOUND for 3D hardware sound support
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
@@ -2353,1811 +1491,268 @@ HW3SOUND for 3D hardware sound support
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+
-
+
-
-
-
-
-
-
-
-
-
-
+
+
-
+
@@ -4166,15 +1761,7 @@ HW3SOUND for 3D hardware sound support
-
-
-
-
-
-
-
-
-
+
@@ -4183,7 +1770,15 @@ HW3SOUND for 3D hardware sound support
-
+
+
+
+
+
+
+
+
+
@@ -4192,7 +1787,7 @@ HW3SOUND for 3D hardware sound support
-
+
@@ -4200,7 +1795,7 @@ HW3SOUND for 3D hardware sound support
-
+
@@ -4209,15 +1804,7 @@ HW3SOUND for 3D hardware sound support
-
-
-
-
-
-
-
-
-
+
@@ -4226,7 +1813,7 @@ HW3SOUND for 3D hardware sound support
-
+
@@ -4235,7 +1822,7 @@ HW3SOUND for 3D hardware sound support
-
+
@@ -4244,7 +1831,7 @@ HW3SOUND for 3D hardware sound support
-
+
@@ -4253,7 +1840,15 @@ HW3SOUND for 3D hardware sound support
-
+
+
+
+
+
+
+
+
+
@@ -4262,15 +1857,7 @@ HW3SOUND for 3D hardware sound support
-
-
-
-
-
-
-
-
-
+
@@ -4279,7 +1866,7 @@ HW3SOUND for 3D hardware sound support
-
+
@@ -4288,7 +1875,15 @@ HW3SOUND for 3D hardware sound support
-
+
+
+
+
+
+
+
+
+
@@ -4297,24 +1892,7 @@ HW3SOUND for 3D hardware sound support
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -4324,120 +1902,39 @@ HW3SOUND for 3D hardware sound support
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -4460,46 +1957,17 @@ HW3SOUND for 3D hardware sound support
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
+
+
-
-
+
+
@@ -4511,37 +1979,8 @@ HW3SOUND for 3D hardware sound support
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -4667,70 +2106,12 @@ HW3SOUND for 3D hardware sound support
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/appveyor.yml b/appveyor.yml
index e0ee99c61..23b9b6281 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,4 +1,4 @@
-version: 2.1.16.{branch}-{build}
+version: 2.1.19.{branch}-{build}
os: MinGW
environment:
@@ -47,7 +47,7 @@ before_build:
- upx -V
- ccache -V
- ccache -s
-- set SRB2_MFLAGS=-C src MINGW=1 WARNINGMODE=1 GCC53=1 CCACHE=1
+- set SRB2_MFLAGS=-C src MINGW=1 WARNINGMODE=1 GCC63=1 CCACHE=1 NOOBJDUMP=1
build_script:
- cmd: mingw32-make.exe %SRB2_MFLAGS% %CONFIGURATION%=1 clean
@@ -58,26 +58,29 @@ after_build:
- cmd: git rev-parse --short %APPVEYOR_REPO_COMMIT%>%TMP%/gitshort.txt
- cmd: set /P GITSHORT=<%TMP%/gitshort.txt
- set BUILD_ARCHIVE=%APPVEYOR_REPO_BRANCH%-%GITSHORT%-%CONFIGURATION%.7z
+- set BUILDSARCHIVE=%APPVEYOR_REPO_BRANCH%-%CONFIGURATION%.7z
- cmd: 7z a %BUILD_ARCHIVE% bin\Mingw\Release -xr!.gitignore
- appveyor PushArtifact %BUILD_ARCHIVE%
+- cmd: copy %BUILD_ARCHIVE% %BUILDSARCHIVE%
+- appveyor PushArtifact %BUILDSARCHIVE%
test: off
-deploy:
- - provider: FTP
- protocol: ftps
- host:
- secure: NsLJEPIBvmwCOj8Tg8RoRQ==
- username:
- secure: ejxi5mvk7oLYu7QtbYojajEPigMy0mokaKhuEVuDZcA=
- password:
- secure: Hbn6Uy3lT0YZ88yFJ3aW4w==
- folder: appveyor
- application:
- active_mode: false
- on:
- branch: master
- appveyor_repo_tag: true
+#deploy:
+# - provider: FTP
+# protocol: ftps
+# host:
+# secure: NsLJEPIBvmwCOj8Tg8RoRQ==
+# username:
+# secure: ejxi5mvk7oLYu7QtbYojajEPigMy0mokaKhuEVuDZcA=
+# password:
+# secure: Hbn6Uy3lT0YZ88yFJ3aW4w==
+# folder: appveyor
+# application:
+# active_mode: false
+# on:
+# branch: master
+# appveyor_repo_tag: true
on_finish:
diff --git a/bin/Mingw/Debug/.gitignore b/bin/Mingw/Debug/.gitignore
index e431dca5d..834f313e3 100644
--- a/bin/Mingw/Debug/.gitignore
+++ b/bin/Mingw/Debug/.gitignore
@@ -1,3 +1,3 @@
-/srb2sdl.exe
-/srb2win.exe
-/r_opengl.dll
+*.exe
+*.mo
+r_opengl.dll
diff --git a/bin/Mingw/Release/.gitignore b/bin/Mingw/Release/.gitignore
index e431dca5d..834f313e3 100644
--- a/bin/Mingw/Release/.gitignore
+++ b/bin/Mingw/Release/.gitignore
@@ -1,3 +1,3 @@
-/srb2sdl.exe
-/srb2win.exe
-/r_opengl.dll
+*.exe
+*.mo
+r_opengl.dll
diff --git a/debian/docs b/debian/docs
index f3d4ef20e..b43bf86b5 100644
--- a/debian/docs
+++ b/debian/docs
@@ -1,2 +1 @@
-readme.txt
-readme.txt
+README.md
diff --git a/objs/.gitignore b/objs/.gitignore
new file mode 100644
index 000000000..35ecd6def
--- /dev/null
+++ b/objs/.gitignore
@@ -0,0 +1,8 @@
+#All folders
+SRB2.res
+depend.dep
+depend.ped
+*.o
+#VC9 folder only
+/VC9/Win32
+/VC9/x64
diff --git a/objs/DC/SDL/Debug/.gitignore b/objs/DC/SDL/Debug/.gitignore
index 867fcb4e0..42c6dc2c6 100644
--- a/objs/DC/SDL/Debug/.gitignore
+++ b/objs/DC/SDL/Debug/.gitignore
@@ -1 +1,2 @@
-/depend.dep
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/DC/SDL/Release/.gitignore b/objs/DC/SDL/Release/.gitignore
index 867fcb4e0..42c6dc2c6 100644
--- a/objs/DC/SDL/Release/.gitignore
+++ b/objs/DC/SDL/Release/.gitignore
@@ -1 +1,2 @@
-/depend.dep
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/Linux/SDL/Debug/.gitignore b/objs/Linux/SDL/Debug/.gitignore
index 8f6d0bdcd..42c6dc2c6 100644
--- a/objs/Linux/SDL/Debug/.gitignore
+++ b/objs/Linux/SDL/Debug/.gitignore
@@ -1,2 +1,2 @@
-/depend.dep
-/*.o
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/Linux/SDL/Release/.gitignore b/objs/Linux/SDL/Release/.gitignore
index 8f6d0bdcd..42c6dc2c6 100644
--- a/objs/Linux/SDL/Release/.gitignore
+++ b/objs/Linux/SDL/Release/.gitignore
@@ -1,2 +1,2 @@
-/depend.dep
-/*.o
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/Linux64/SDL/Debug/.gitignore b/objs/Linux64/SDL/Debug/.gitignore
index 8f6d0bdcd..42c6dc2c6 100644
--- a/objs/Linux64/SDL/Debug/.gitignore
+++ b/objs/Linux64/SDL/Debug/.gitignore
@@ -1,2 +1,2 @@
-/depend.dep
-/*.o
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/Linux64/SDL/Release/.gitignore b/objs/Linux64/SDL/Release/.gitignore
index 8f6d0bdcd..42c6dc2c6 100644
--- a/objs/Linux64/SDL/Release/.gitignore
+++ b/objs/Linux64/SDL/Release/.gitignore
@@ -1,2 +1,2 @@
-/depend.dep
-/*.o
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/Mingw/Debug/.gitignore b/objs/Mingw/Debug/.gitignore
index da4b3e912..42c6dc2c6 100644
--- a/objs/Mingw/Debug/.gitignore
+++ b/objs/Mingw/Debug/.gitignore
@@ -1,3 +1,2 @@
-/SRB2.res
-/depend.dep
-/*.o
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/Mingw/Release/.gitignore b/objs/Mingw/Release/.gitignore
index da4b3e912..42c6dc2c6 100644
--- a/objs/Mingw/Release/.gitignore
+++ b/objs/Mingw/Release/.gitignore
@@ -1,3 +1,2 @@
-/SRB2.res
-/depend.dep
-/*.o
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/Mingw/SDL/Debug/.gitignore b/objs/Mingw/SDL/Debug/.gitignore
index da4b3e912..42c6dc2c6 100644
--- a/objs/Mingw/SDL/Debug/.gitignore
+++ b/objs/Mingw/SDL/Debug/.gitignore
@@ -1,3 +1,2 @@
-/SRB2.res
-/depend.dep
-/*.o
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/Mingw/SDL/Release/.gitignore b/objs/Mingw/SDL/Release/.gitignore
index da4b3e912..42c6dc2c6 100644
--- a/objs/Mingw/SDL/Release/.gitignore
+++ b/objs/Mingw/SDL/Release/.gitignore
@@ -1,3 +1,2 @@
-/SRB2.res
-/depend.dep
-/*.o
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/Mingw64/Debug/.gitignore b/objs/Mingw64/Debug/.gitignore
index da4b3e912..42c6dc2c6 100644
--- a/objs/Mingw64/Debug/.gitignore
+++ b/objs/Mingw64/Debug/.gitignore
@@ -1,3 +1,2 @@
-/SRB2.res
-/depend.dep
-/*.o
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/Mingw64/Release/.gitignore b/objs/Mingw64/Release/.gitignore
index da4b3e912..42c6dc2c6 100644
--- a/objs/Mingw64/Release/.gitignore
+++ b/objs/Mingw64/Release/.gitignore
@@ -1,3 +1,2 @@
-/SRB2.res
-/depend.dep
-/*.o
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/Mingw64/SDL/Debug/.gitignore b/objs/Mingw64/SDL/Debug/.gitignore
index da4b3e912..42c6dc2c6 100644
--- a/objs/Mingw64/SDL/Debug/.gitignore
+++ b/objs/Mingw64/SDL/Debug/.gitignore
@@ -1,3 +1,2 @@
-/SRB2.res
-/depend.dep
-/*.o
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/Mingw64/SDL/Release/.gitignore b/objs/Mingw64/SDL/Release/.gitignore
index da4b3e912..42c6dc2c6 100644
--- a/objs/Mingw64/SDL/Release/.gitignore
+++ b/objs/Mingw64/SDL/Release/.gitignore
@@ -1,3 +1,2 @@
-/SRB2.res
-/depend.dep
-/*.o
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/PS3/SDL/Debug/.gitignore b/objs/PS3/SDL/Debug/.gitignore
index 8f6d0bdcd..42c6dc2c6 100644
--- a/objs/PS3/SDL/Debug/.gitignore
+++ b/objs/PS3/SDL/Debug/.gitignore
@@ -1,2 +1,2 @@
-/depend.dep
-/*.o
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/PS3/SDL/Release/.gitignore b/objs/PS3/SDL/Release/.gitignore
index 8f6d0bdcd..42c6dc2c6 100644
--- a/objs/PS3/SDL/Release/.gitignore
+++ b/objs/PS3/SDL/Release/.gitignore
@@ -1,2 +1,2 @@
-/depend.dep
-/*.o
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/PSP/SDL/Release/.gitignore b/objs/PSP/SDL/Release/.gitignore
index 867fcb4e0..42c6dc2c6 100644
--- a/objs/PSP/SDL/Release/.gitignore
+++ b/objs/PSP/SDL/Release/.gitignore
@@ -1 +1,2 @@
-/depend.dep
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/SDL/Release/.gitignore b/objs/SDL/Release/.gitignore
index 4a262f94f..42c6dc2c6 100644
--- a/objs/SDL/Release/.gitignore
+++ b/objs/SDL/Release/.gitignore
@@ -1 +1,2 @@
-/depend.ped
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/VC/.gitignore b/objs/VC/.gitignore
index e69de29bb..42c6dc2c6 100644
--- a/objs/VC/.gitignore
+++ b/objs/VC/.gitignore
@@ -0,0 +1,2 @@
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/VC9/.gitignore b/objs/VC9/.gitignore
index 205fe45de..42c6dc2c6 100644
--- a/objs/VC9/.gitignore
+++ b/objs/VC9/.gitignore
@@ -1,2 +1,2 @@
-/Win32
-/x64
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/Wii/SDL/Debug/.gitignore b/objs/Wii/SDL/Debug/.gitignore
index 8f6d0bdcd..42c6dc2c6 100644
--- a/objs/Wii/SDL/Debug/.gitignore
+++ b/objs/Wii/SDL/Debug/.gitignore
@@ -1,2 +1,2 @@
-/depend.dep
-/*.o
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/Wii/SDL/Release/.gitignore b/objs/Wii/SDL/Release/.gitignore
index 8f6d0bdcd..42c6dc2c6 100644
--- a/objs/Wii/SDL/Release/.gitignore
+++ b/objs/Wii/SDL/Release/.gitignore
@@ -1,2 +1,2 @@
-/depend.dep
-/*.o
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/WinCE/SDL/Release/.gitignore b/objs/WinCE/SDL/Release/.gitignore
index 867fcb4e0..42c6dc2c6 100644
--- a/objs/WinCE/SDL/Release/.gitignore
+++ b/objs/WinCE/SDL/Release/.gitignore
@@ -1 +1,2 @@
-/depend.dep
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/djgppdos/Debug/.gitignore b/objs/djgppdos/Debug/.gitignore
index 867fcb4e0..42c6dc2c6 100644
--- a/objs/djgppdos/Debug/.gitignore
+++ b/objs/djgppdos/Debug/.gitignore
@@ -1 +1,2 @@
-/depend.dep
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/djgppdos/Release/.gitignore b/objs/djgppdos/Release/.gitignore
index 867fcb4e0..42c6dc2c6 100644
--- a/objs/djgppdos/Release/.gitignore
+++ b/objs/djgppdos/Release/.gitignore
@@ -1 +1,2 @@
-/depend.dep
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/nds/Debug/.gitignore b/objs/nds/Debug/.gitignore
index 8f6d0bdcd..42c6dc2c6 100644
--- a/objs/nds/Debug/.gitignore
+++ b/objs/nds/Debug/.gitignore
@@ -1,2 +1,2 @@
-/depend.dep
-/*.o
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/nds/Release/.gitignore b/objs/nds/Release/.gitignore
index 8f6d0bdcd..42c6dc2c6 100644
--- a/objs/nds/Release/.gitignore
+++ b/objs/nds/Release/.gitignore
@@ -1,2 +1,2 @@
-/depend.dep
-/*.o
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index ba354c289..da8438a59 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -231,6 +231,7 @@ if(${SRB2_CONFIG_HAVE_BLUA})
add_definitions(-DHAVE_BLUA)
set(SRB2_LUA_SOURCES
lua_baselib.c
+ lua_blockmaplib.c
lua_consolelib.c
lua_hooklib.c
lua_hudlib.c
@@ -390,18 +391,25 @@ if(${SRB2_CONFIG_HWRENDER} AND ${SRB2_CONFIG_STATIC_OPENGL})
endif()
if(${SRB2_CONFIG_USEASM})
+ #SRB2_ASM_FLAGS can be used to pass flags to either nasm or yasm.
+ if(${CMAKE_SYSTEM} MATCHES "Linux")
+ set(SRB2_ASM_FLAGS "-DLINUX ${SRB2_ASM_FLAGS}")
+ endif()
+
if(${SRB2_CONFIG_YASM})
set(CMAKE_ASM_YASM_SOURCE_FILE_EXTENSIONS ${CMAKE_ASM_YASM_SOURCE_FILE_EXTENSIONS} nas)
+ set(CMAKE_ASM_YASM_FLAGS "${SRB2_ASM_FLAGS}" CACHE STRING "Flags used by the assembler during all build types.")
enable_language(ASM_YASM)
else()
set(CMAKE_ASM_NASM_SOURCE_FILE_EXTENSIONS ${CMAKE_ASM_NASM_SOURCE_FILE_EXTENSIONS} nas)
+ set(CMAKE_ASM_NASM_FLAGS "${SRB2_ASM_FLAGS}" CACHE STRING "Flags used by the assembler during all build types.")
enable_language(ASM_NASM)
endif()
set(SRB2_USEASM ON)
add_definitions(-DUSEASM)
else()
set(SRB2_USEASM OFF)
- add_definitions(-DNOASM -DNONX86)
+ add_definitions(-DNONX86 -DNORUSEASM)
endif()
# Targets
diff --git a/src/Makefile b/src/Makefile
index ce4b569ee..426dc2289 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -179,6 +179,9 @@ endif
ifdef LINUX
UNIXCOMMON=1
+ifndef NOGME
+HAVE_LIBGME=1
+endif
endif
ifdef SOLARIS
@@ -315,6 +318,13 @@ LIBS+=$(PNG_LDFLAGS)
CFLAGS+=$(PNG_CFLAGS)
endif
+ZLIB_PKGCONFIG?=zlib
+ZLIB_CFLAGS?=$(shell $(PKG_CONFIG) $(ZLIB_PKGCONFIG) --cflags)
+ZLIB_LDFLAGS?=$(shell $(PKG_CONFIG) $(ZLIB_PKGCONFIG) --libs)
+
+LIBS+=$(ZLIB_LDFLAGS)
+CFLAGS+=$(ZLIB_CFLAGS)
+
ifdef HAVE_LIBGME
OPTS+=-DHAVE_LIBGME
@@ -366,6 +376,14 @@ endif
OPTS:=-fno-exceptions $(OPTS)
+ifdef MOBJCONSISTANCY
+ OPTS+=-DMOBJCONSISTANCY
+endif
+
+ifdef PACKETDROP
+ OPTS+=-DPACKETDROP
+endif
+
ifdef DEBUGMODE
# build with debugging information
@@ -375,7 +393,7 @@ ifdef GCC48
else
CFLAGS+=-O0
endif
- CFLAGS+= -Wall -DPARANOIA -DRANGECHECK
+ CFLAGS+= -Wall -DPARANOIA -DRANGECHECK -DPACKETDROP -DMOBJCONSISTANCY
else
@@ -493,13 +511,11 @@ OBJS:=$(i_main_o) \
# For reference, this is the command I use to build a srb2.pot file from the source code.
# (The listed source files are the ones containing translated strings).
# FILES=""; for file in `find ./ | grep "\.c" | grep -v svn`; do [ "`grep "M_GetText(" $file`" ] && FILES="$FILES $file"; done; xgettext -d srb2 -o locale/srb2.pot -kM_GetText -F --no-wrap $FILES
-ifndef NOGETTEXT
ifdef GETTEXT
POS:=$(BIN)/en.mo
OPTS+=-DGETTEXT
endif
-endif
ifdef DJGPPDOS
all: pre-build $(BIN)/$(EXENAME)
diff --git a/src/Makefile.cfg b/src/Makefile.cfg
index 72404becc..5bf7f247d 100644
--- a/src/Makefile.cfg
+++ b/src/Makefile.cfg
@@ -7,6 +7,23 @@
# and other things
#
+
+ifdef GCC63
+GCC62=1
+endif
+
+ifdef GCC62
+GCC61=1
+endif
+
+ifdef GCC61
+GCC54=1
+endif
+
+ifdef GCC54
+GCC53=1
+endif
+
ifdef GCC53
GCC52=1
endif
@@ -164,19 +181,29 @@ ifdef GCC45
WFLAGS+=-Wunsuffixed-float-constants
endif
endif
+
ifdef NOLDWARNING
LDFLAGS+=-Wl,--as-needed
endif
+
ifdef ERRORMODE
WFLAGS+=-Werror
endif
+
+WFLAGS+=$(OLDWFLAGS)
+
ifdef GCC43
#WFLAGS+=-Wno-error=clobbered
endif
ifdef GCC46
WFLAGS+=-Wno-error=suggest-attribute=noreturn
endif
-WFLAGS+=$(OLDWFLAGS)
+ifdef GCC54
+ WFLAGS+=-Wno-logical-op -Wno-error=logical-op
+endif
+ifdef GCC61
+ WFLAGS+=-Wno-tautological-compare -Wno-error=tautological-compare
+endif
#indicate platform and what interface use with
@@ -256,9 +283,6 @@ else
ifdef LINUX
NASMFORMAT=elf -DLINUX
SDL=1
-ifndef NOGETTEXT
- GETTEXT=1
-endif
ifdef LINUX64
OBJDIR:=$(OBJDIR)/Linux64
BIN:=$(BIN)/Linux64
@@ -294,9 +318,6 @@ else
ifdef MINGW64
INTERFACE=win32
#NASMFORMAT=win64
-ifndef NOGETTEXT
- #GETTEXT=1
-endif
OBJDIR:=$(OBJDIR)/Mingw64
BIN:=$(BIN)/Mingw64
else
@@ -327,9 +348,6 @@ else
ifdef MINGW
INTERFACE=win32
NASMFORMAT=win32
-ifndef NOGETTEXT
- GETTEXT=1
-endif
OBJDIR:=$(OBJDIR)/Mingw
BIN:=$(BIN)/Mingw
else
diff --git a/src/android/i_system.c b/src/android/i_system.c
index 150cbd505..58fca7c19 100644
--- a/src/android/i_system.c
+++ b/src/android/i_system.c
@@ -258,6 +258,18 @@ INT32 I_PutEnv(char *variable)
return -1;
}
+INT32 I_ClipboardCopy(const char *data, size_t size)
+{
+ (void)data;
+ (void)size;
+ return -1;
+}
+
+char *I_ClipboardPaste(void)
+{
+ return NULL;
+}
+
void I_RegisterSysCommands(void) {}
#include "../sdl/dosstr.c"
diff --git a/src/b_bot.c b/src/b_bot.c
index 0f2c80d55..dc65c9c16 100644
--- a/src/b_bot.c
+++ b/src/b_bot.c
@@ -212,7 +212,7 @@ boolean B_CheckRespawn(player_t *player)
// Check if Sonic is busy first.
// If he's doing any of these things, he probably doesn't want to see us.
- if (sonic->player->pflags & (PF_GLIDING|PF_SLIDING|PF_NIGHTSMODE)
+ if (sonic->player->pflags & (PF_GLIDING|PF_SLIDING|PF_BOUNCING)
|| (sonic->player->panim != PA_IDLE && sonic->player->panim != PA_WALK)
|| (sonic->player->powers[pw_carry]))
return false;
diff --git a/src/blua/Makefile.cfg b/src/blua/Makefile.cfg
index e3fb3df46..8d2e73714 100644
--- a/src/blua/Makefile.cfg
+++ b/src/blua/Makefile.cfg
@@ -48,4 +48,5 @@ OBJS:=$(OBJS) \
$(OBJDIR)/lua_skinlib.o \
$(OBJDIR)/lua_thinkerlib.o \
$(OBJDIR)/lua_maplib.o \
+ $(OBJDIR)/lua_blockmaplib.o \
$(OBJDIR)/lua_hudlib.o
diff --git a/src/command.c b/src/command.c
index 6534fed3d..d72f845ad 100644
--- a/src/command.c
+++ b/src/command.c
@@ -1165,7 +1165,7 @@ found:
if (var == &cv_forceskin)
{
var->value = R_SkinAvailable(var->string);
- if (!R_SkinUnlock(var->value))
+ if (!R_SkinUsable(-1, var->value))
var->value = -1;
}
else
@@ -1361,6 +1361,16 @@ static void CV_SetCVar(consvar_t *var, const char *value, boolean stealth)
return;
}
+ if (var == &cv_forceskin)
+ {
+ INT32 skin = R_SkinAvailable(value);
+ if ((stricmp(value, "None")) && ((skin == -1) || !R_SkinUsable(-1, skin)))
+ {
+ CONS_Printf("Please provide a valid skin name (\"None\" disables).\n");
+ return;
+ }
+ }
+
// Only add to netcmd buffer if in a netgame, otherwise, just change it.
if (netgame || multiplayer)
{
@@ -1478,7 +1488,7 @@ void CV_AddValue(consvar_t *var, INT32 increment)
else if (newvalue >= numskins)
newvalue = -1;
} while ((oldvalue != newvalue)
- && !(R_SkinUnlock(newvalue)));
+ && !(R_SkinUsable(-1, newvalue)));
}
else
newvalue = var->value + increment;
@@ -1551,34 +1561,27 @@ void CV_AddValue(consvar_t *var, INT32 increment)
if (var == &cv_chooseskin)
{
// Special case for the chooseskin variable, used only directly from the menu
- if (increment > 0) // Going up!
+ newvalue = var->value - 1;
+ do
{
- newvalue = var->value - 1;
- do
+ if (increment > 0) // Going up!
{
newvalue++;
if (newvalue == MAXSKINS)
newvalue = 0;
- } while (var->PossibleValue[newvalue].strvalue == NULL);
- var->value = newvalue + 1;
- var->string = var->PossibleValue[newvalue].strvalue;
- var->func();
- return;
- }
- else if (increment < 0) // Going down!
- {
- newvalue = var->value - 1;
- do
+ }
+ else if (increment < 0) // Going down!
{
newvalue--;
if (newvalue == -1)
newvalue = MAXSKINS-1;
- } while (var->PossibleValue[newvalue].strvalue == NULL);
- var->value = newvalue + 1;
- var->string = var->PossibleValue[newvalue].strvalue;
- var->func();
- return;
- }
+ }
+ } while (var->PossibleValue[newvalue].strvalue == NULL);
+
+ var->value = newvalue + 1;
+ var->string = var->PossibleValue[newvalue].strvalue;
+ var->func();
+ return;
}
#ifdef PARANOIA
if (currentindice == -1)
diff --git a/src/console.c b/src/console.c
index be4eee210..fff9ba96c 100644
--- a/src/console.c
+++ b/src/console.c
@@ -84,19 +84,23 @@ UINT32 con_scalefactor; // text size scale factor
// hold 32 last lines of input for history
#define CON_MAXPROMPTCHARS 256
-#define CON_PROMPTCHAR '>'
+#define CON_PROMPTCHAR '$'
static char inputlines[32][CON_MAXPROMPTCHARS]; // hold last 32 prompt lines
static INT32 inputline; // current input line number
static INT32 inputhist; // line number of history input line to restore
-static size_t input_cx; // position in current input line
+static size_t input_cur; // position of cursor in line
+static size_t input_sel; // position of selection marker (I.E.: anything between this and input_cur is "selected")
+static size_t input_len; // length of current line, used to bound cursor and such
+// notice: input does NOT include the "$" at the start of the line. - 11/3/16
// protos.
static void CON_InputInit(void);
static void CON_RecalcSize(void);
static void CONS_hudlines_Change(void);
+static void CONS_backcolor_Change(void);
static void CON_DrawBackpic(patch_t *pic, INT32 startx, INT32 destwidth);
//static void CON_DrawBackpic2(pic_t *pic, INT32 startx, INT32 destwidth);
@@ -129,10 +133,12 @@ static CV_PossibleValue_t backpic_cons_t[] = {{0, "translucent"}, {1, "picture"}
// whether to use console background picture, or translucent mode
static consvar_t cons_backpic = {"con_backpic", "translucent", CV_SAVE, backpic_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-static CV_PossibleValue_t backcolor_cons_t[] = {{0, "White"}, {1, "Orange"},
- {2, "Blue"}, {3, "Green"}, {4, "Gray"},
- {5, "Red"}, {0, NULL}};
-consvar_t cons_backcolor = {"con_backcolor", "3", CV_SAVE, backcolor_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+static CV_PossibleValue_t backcolor_cons_t[] = {{0, "White"}, {1, "Gray"}, {2, "Brown"},
+ {3, "Red"}, {4, "Orange"}, {5, "Yellow"},
+ {6, "Green"}, {7, "Blue"}, {8, "Purple"},
+ {9, "Magenta"}, {10, "Aqua"},
+ {0, NULL}};
+consvar_t cons_backcolor = {"con_backcolor", "Green", CV_CALL|CV_SAVE, backcolor_cons_t, CONS_backcolor_Change, 0, NULL, NULL, 0, 0, NULL};
static void CON_Print(char *msg);
@@ -219,8 +225,9 @@ static void CONS_Bind_f(void)
// CONSOLE SETUP
//======================================================================
-// Prepare a colormap for GREEN ONLY translucency over background
-//
+// Font colormap colors
+// TODO: This could probably be improved somehow...
+// These colormaps are 99% identical, with just a few changed bytes
UINT8 *yellowmap;
UINT8 *purplemap;
UINT8 *lgreenmap;
@@ -229,44 +236,52 @@ UINT8 *graymap;
UINT8 *redmap;
UINT8 *orangemap;
-// Console BG colors
-UINT8 *cwhitemap;
-UINT8 *corangemap;
-UINT8 *cbluemap;
-UINT8 *cgreenmap;
-UINT8 *cgraymap;
-UINT8 *credmap;
+// Console BG color
+UINT8 *consolebgmap = NULL;
-void CON_ReSetupBackColormap(UINT16 num)
+void CON_SetupBackColormap(void)
{
- UINT16 i, j;
- UINT8 k;
- UINT8 *pal = W_CacheLumpName(R_GetPalname(num), PU_CACHE);
+ UINT16 i, palsum;
+ UINT8 j, palindex, shift;
+ UINT8 *pal = W_CacheLumpName(GetPalette(), PU_CACHE);
- // setup the green translucent background colormaps
- for (i = 0, k = 0; i < 768; i += 3, k++)
+ if (!consolebgmap)
+ consolebgmap = (UINT8 *)Z_Malloc(256, PU_STATIC, NULL);
+
+ shift = 6; // 12 colors -- shift of 7 means 6 colors
+ switch (cons_backcolor.value)
{
- j = pal[i] + pal[i+1] + pal[i+2];
- cwhitemap[k] = (UINT8)(15 - (j>>6));
- corangemap[k] = (UINT8)(63 - (j>>6));
- cbluemap[k] = (UINT8)(159 - (j>>6));
- cgreenmap[k] = (UINT8)(111 - (j>>6));
- cgraymap[k] = (UINT8)(31 - (j>>6));
- credmap[k] = (UINT8)(47 - (j>>6));
+ case 0: palindex = 15; break; // White
+ case 1: palindex = 31; break; // Gray
+ case 2: palindex = 239; break; // Brown
+ case 3: palindex = 47; break; // Red
+ case 4: palindex = 63; break; // Orange
+ case 5: palindex = 79; shift = 7; break; // Yellow
+ case 6: palindex = 111; break; // Green
+ case 7: palindex = 159; break; // Blue
+ case 8: palindex = 199; shift = 7; break; // Purple
+ case 9: palindex = 187; break; // Magenta
+ case 10: palindex = 139; break; // Aqua
+ // Default green
+ default: palindex = 175; break;
+}
+
+ // setup background colormap
+ for (i = 0, j = 0; i < 768; i += 3, j++)
+ {
+ palsum = (pal[i] + pal[i+1] + pal[i+2]) >> shift;
+ consolebgmap[j] = (UINT8)(palindex - palsum);
}
}
-static void CON_SetupBackColormap(void)
+static void CONS_backcolor_Change(void)
{
- INT32 i, j, k;
- UINT8 *pal;
+ CON_SetupBackColormap();
+}
- cwhitemap = (UINT8 *)Z_Malloc(256, PU_STATIC, NULL);
- corangemap = (UINT8 *)Z_Malloc(256, PU_STATIC, NULL);
- cbluemap = (UINT8 *)Z_Malloc(256, PU_STATIC, NULL);
- cgreenmap = (UINT8 *)Z_Malloc(256, PU_STATIC, NULL);
- cgraymap = (UINT8 *)Z_Malloc(256, PU_STATIC, NULL);
- credmap = (UINT8 *)Z_Malloc(256, PU_STATIC, NULL);
+static void CON_SetupColormaps(void)
+{
+ INT32 i;
yellowmap = (UINT8 *)Z_Malloc(256, PU_STATIC, NULL);
graymap = (UINT8 *)Z_Malloc(256, PU_STATIC, NULL);
@@ -276,20 +291,6 @@ static void CON_SetupBackColormap(void)
redmap = (UINT8 *)Z_Malloc(256, PU_STATIC, NULL);
orangemap = (UINT8 *)Z_Malloc(256, PU_STATIC, NULL);
- pal = W_CacheLumpName("PLAYPAL", PU_CACHE);
-
- // setup the green translucent background colormaps
- for (i = 0, k = 0; i < 768; i += 3, k++)
- {
- j = pal[i] + pal[i+1] + pal[i+2];
- cwhitemap[k] = (UINT8)(15 - (j>>6));
- corangemap[k] = (UINT8)(63 - (j>>6));
- cbluemap[k] = (UINT8)(159 - (j>>6));
- cgreenmap[k] = (UINT8)(111 - (j>>6));
- cgraymap[k] = (UINT8)(31 - (j>>6));
- credmap[k] = (UINT8)(47 - (j>>6));
- }
-
// setup the other colormaps, for console text
// these don't need to be aligned, unless you convert the
@@ -320,6 +321,9 @@ static void CON_SetupBackColormap(void)
redmap[9] = (UINT8)32;
orangemap[3] = (UINT8)52;
orangemap[9] = (UINT8)57;
+
+ // Init back colormap
+ CON_SetupBackColormap();
}
// Setup the console text buffer
@@ -343,7 +347,7 @@ void CON_Init(void)
con_width = 0;
CON_RecalcSize();
- CON_SetupBackColormap();
+ CON_SetupColormaps();
//note: CON_Ticker should always execute at least once before D_Display()
con_clipviewtop = -1; // -1 does not clip
@@ -386,14 +390,10 @@ void CON_Init(void)
//
static void CON_InputInit(void)
{
- INT32 i;
-
// prepare the first prompt line
memset(inputlines, 0, sizeof (inputlines));
- for (i = 0; i < 32; i++)
- inputlines[i][0] = CON_PROMPTCHAR;
inputline = 0;
- input_cx = 1;
+ input_cur = input_sel = input_len = 0;
}
//======================================================================
@@ -618,13 +618,91 @@ void CON_Ticker(void)
}
}
+//
+// ----
+//
+// Shortcuts for adding and deleting characters, strings, and sections
+// Necessary due to moving cursor
+//
+
+static void CON_InputClear(void)
+{
+ memset(inputlines[inputline], 0, CON_MAXPROMPTCHARS);
+ input_cur = input_sel = input_len = 0;
+}
+
+static void CON_InputSetString(const char *c)
+{
+ memset(inputlines[inputline], 0, CON_MAXPROMPTCHARS);
+ strcpy(inputlines[inputline], c);
+ input_cur = input_sel = input_len = strlen(c);
+}
+
+static void CON_InputAddString(const char *c)
+{
+ size_t csize = strlen(c);
+ if (input_len + csize > CON_MAXPROMPTCHARS-1)
+ return;
+ if (input_cur != input_len)
+ memmove(&inputlines[inputline][input_cur+csize], &inputlines[inputline][input_cur], input_len-input_cur);
+ memcpy(&inputlines[inputline][input_cur], c, csize);
+ input_len += csize;
+ input_sel = (input_cur += csize);
+}
+
+static void CON_InputDelSelection(void)
+{
+ size_t start, end, len;
+ if (input_cur > input_sel)
+ {
+ start = input_sel;
+ end = input_cur;
+ }
+ else
+ {
+ start = input_cur;
+ end = input_sel;
+ }
+ len = (end - start);
+
+ if (end != input_len)
+ memmove(&inputlines[inputline][start], &inputlines[inputline][end], input_len-end);
+ memset(&inputlines[inputline][input_len - len], 0, len);
+
+ input_len -= len;
+ input_sel = input_cur = start;
+}
+
+static void CON_InputAddChar(char c)
+{
+ if (input_len >= CON_MAXPROMPTCHARS-1)
+ return;
+ if (input_cur != input_len)
+ memmove(&inputlines[inputline][input_cur+1], &inputlines[inputline][input_cur], input_len-input_cur);
+ inputlines[inputline][input_cur++] = c;
+ inputlines[inputline][++input_len] = 0;
+ input_sel = input_cur;
+}
+
+static void CON_InputDelChar(void)
+{
+ if (!input_cur)
+ return;
+ if (input_cur != input_len)
+ memmove(&inputlines[inputline][input_cur-1], &inputlines[inputline][input_cur], input_len-input_cur);
+ inputlines[inputline][--input_len] = 0;
+ input_sel = --input_cur;
+}
+
+//
+// ----
+//
+
// Handles console key input
//
boolean CON_Responder(event_t *ev)
{
- static boolean consdown;
- static boolean shiftdown;
- static boolean ctrldown;
+ static UINT8 consdown = false; // console is treated differently due to rare usage
// sequential completions a la 4dos
static char completion[80];
@@ -639,13 +717,8 @@ boolean CON_Responder(event_t *ev)
// let go keyup events, don't eat them
if (ev->type != ev_keydown && ev->type != ev_console)
{
- if (ev->data1 == KEY_LSHIFT || ev->data1 == KEY_RSHIFT)
- shiftdown = false;
- else if (ev->data1 == KEY_LCTRL || ev->data1 == KEY_RCTRL)
- ctrldown = false;
- else if (ev->data1 == gamecontrol[gc_console][0] || ev->data1 == gamecontrol[gc_console][1])
+ if (ev->data1 == gamecontrol[gc_console][0] || ev->data1 == gamecontrol[gc_console][1])
consdown = false;
-
return false;
}
@@ -684,94 +757,110 @@ boolean CON_Responder(event_t *ev)
consoletoggle = true;
return true;
}
-
}
- // eat shift only if console active
- if (key == KEY_LSHIFT || key == KEY_RSHIFT)
- {
- shiftdown = true;
+ // Always eat ctrl/shift/alt if console open, so the menu doesn't get ideas
+ if (key == KEY_LSHIFT || key == KEY_RSHIFT
+ || key == KEY_LCTRL || key == KEY_RCTRL
+ || key == KEY_LALT || key == KEY_RALT)
return true;
- }
- // same for ctrl
- if (key == KEY_LCTRL || key == KEY_RCTRL)
+ // ctrl modifier -- changes behavior, adds shortcuts
+ if (ctrldown)
{
- ctrldown = true;
- return true;
+ // show all cvars/commands that match what we have inputted
+ if (key == KEY_TAB)
+ {
+ size_t i, len;
+
+ if (!completion[0])
+ {
+ if (!input_len || input_len >= 40 || strchr(inputlines[inputline], ' '))
+ return true;
+ strcpy(completion, inputlines[inputline]);
+ comskips = varskips = 0;
+ }
+ len = strlen(completion);
+
+ //first check commands
+ CONS_Printf("\nCommands:\n");
+ for (i = 0, cmd = COM_CompleteCommand(completion, i); cmd; cmd = COM_CompleteCommand(completion, ++i))
+ CONS_Printf(" \x83" "%s" "\x80" "%s\n", completion, cmd+len);
+ if (i == 0) CONS_Printf(" (none)\n");
+
+ //now we move on to CVARs
+ CONS_Printf("Variables:\n");
+ for (i = 0, cmd = CV_CompleteVar(completion, i); cmd; cmd = CV_CompleteVar(completion, ++i))
+ CONS_Printf(" \x83" "%s" "\x80" "%s\n", completion, cmd+len);
+ if (i == 0) CONS_Printf(" (none)\n");
+
+ return true;
+ }
+ // ---
+
+ if (key == KEY_HOME) // oldest text in buffer
+ {
+ con_scrollup = (con_totallines-((con_curlines-16)>>3));
+ return true;
+ }
+ else if (key == KEY_END) // most recent text in buffer
+ {
+ con_scrollup = 0;
+ return true;
+ }
+
+ if (key == 'x' || key == 'X')
+ {
+ if (input_sel > input_cur)
+ I_ClipboardCopy(&inputlines[inputline][input_cur], input_sel-input_cur);
+ else
+ I_ClipboardCopy(&inputlines[inputline][input_sel], input_cur-input_sel);
+ CON_InputDelSelection();
+ completion[0] = 0;
+ return true;
+ }
+ else if (key == 'c' || key == 'C')
+ {
+ if (input_sel > input_cur)
+ I_ClipboardCopy(&inputlines[inputline][input_cur], input_sel-input_cur);
+ else
+ I_ClipboardCopy(&inputlines[inputline][input_sel], input_cur-input_sel);
+ return true;
+ }
+ else if (key == 'v' || key == 'V')
+ {
+ const char *paste = I_ClipboardPaste();
+ if (input_sel != input_cur)
+ CON_InputDelSelection();
+ if (paste != NULL)
+ CON_InputAddString(paste);
+ completion[0] = 0;
+ return true;
+ }
+
+ // Select all
+ if (key == 'a' || key == 'A')
+ {
+ input_sel = 0;
+ input_cur = input_len;
+ return true;
+ }
+
+ // don't eat the key
+ return false;
}
// command completion forward (tab) and backward (shift-tab)
if (key == KEY_TAB)
{
- // show all cvars/commands that match what we have inputted
- if (ctrldown)
- {
- UINT32 i;
- size_t stop = input_cx - 1;
- char nameremainder[255];
-
- if (input_cx < 2 || strlen(inputlines[inputline]+1) >= 80)
- return true;
-
- strcpy(completion, inputlines[inputline]+1);
-
- // trimming: stop at the first newline
- for (i = 0; i < input_cx - 1; ++i)
- {
- if (completion[i] == ' ')
- {
- completion[i] = '\0';
- stop = i;
- break;
- }
- }
-
- i = 0;
-
- //first check commands
- CONS_Printf("\nCommands:\n");
-
- for (cmd = COM_CompleteCommand(completion, i); cmd; cmd = COM_CompleteCommand(completion, i))
- {
- strncpy(nameremainder, cmd+(stop), strlen(cmd)-(stop));
- nameremainder[strlen(cmd)-(stop)] = '\0';
-
- CONS_Printf(" \x83" "%s" "\x80" "%s\n", completion, nameremainder);
- ++i;
- }
- if (i == 0)
- CONS_Printf(" (none)\n");
-
- i = 0;
-
- //now we move on to CVARs
- CONS_Printf("Variables:\n");
-
- for (cmd = CV_CompleteVar(completion, i); cmd; cmd = CV_CompleteVar(completion, i))
- {
- strncpy(nameremainder, cmd+(stop), strlen(cmd)-(stop));
- nameremainder[strlen(cmd)-(stop)] = '\0';
-
- CONS_Printf(" \x83" "%s" "\x80" "%s\n", completion, nameremainder);
- ++i;
- }
- if (i == 0)
- CONS_Printf(" (none)\n");
-
- return true;
- }
-
// sequential command completion forward and backward
// remember typing for several completions (a-la-4dos)
- if (inputlines[inputline][input_cx-1] != ' ')
+ if (!completion[0])
{
- if (strlen(inputlines[inputline]+1) < 80)
- strcpy(completion, inputlines[inputline]+1);
- else
- completion[0] = 0;
-
+ if (!input_len || input_len >= 40 || strchr(inputlines[inputline], ' '))
+ return true;
+ strcpy(completion, inputlines[inputline]);
comskips = varskips = 0;
}
else
@@ -783,37 +872,26 @@ boolean CON_Responder(event_t *ev)
if (--varskips < 0)
comskips = -comskips - 2;
}
- else if (comskips > 0)
- comskips--;
+ else if (comskips > 0) comskips--;
}
else
{
- if (comskips < 0)
- varskips++;
- else
- comskips++;
+ if (comskips < 0) varskips++;
+ else comskips++;
}
}
if (comskips >= 0)
{
cmd = COM_CompleteCommand(completion, comskips);
- if (!cmd)
- // dirty: make sure if comskips is zero, to have a neg value
+ if (!cmd) // dirty: make sure if comskips is zero, to have a neg value
comskips = -comskips - 1;
}
if (comskips < 0)
cmd = CV_CompleteVar(completion, varskips);
if (cmd)
- {
- memset(inputlines[inputline]+1, 0, CON_MAXPROMPTCHARS-1);
- strcpy(inputlines[inputline]+1, cmd);
- input_cx = strlen(cmd) + 1;
- inputlines[inputline][input_cx] = ' ';
- input_cx++;
- inputlines[inputline][input_cx] = 0;
- }
+ CON_InputSetString(va("%s ", cmd));
else
{
if (comskips > 0)
@@ -839,47 +917,80 @@ boolean CON_Responder(event_t *ev)
return true;
}
- if (key == KEY_HOME) // oldest text in buffer
+ if (key == KEY_LEFTARROW)
{
- con_scrollup = (con_totallines-((con_curlines-16)>>3));
+ if (input_cur != 0)
+ --input_cur;
+ if (!shiftdown)
+ input_sel = input_cur;
return true;
}
- else if (key == KEY_END) // most recent text in buffer
+ else if (key == KEY_RIGHTARROW)
{
- con_scrollup = 0;
+ if (input_cur < input_len)
+ ++input_cur;
+ if (!shiftdown)
+ input_sel = input_cur;
return true;
}
+ else if (key == KEY_HOME)
+ {
+ input_cur = 0;
+ if (!shiftdown)
+ input_sel = input_cur;
+ return true;
+ }
+ else if (key == KEY_END)
+ {
+ input_cur = input_len;
+ if (!shiftdown)
+ input_sel = input_cur;
+ return true;
+ }
+
+ // At this point we're messing with input
+ // Clear completion
+ completion[0] = 0;
// command enter
if (key == KEY_ENTER)
{
- if (input_cx < 2)
+ if (!input_len)
return true;
// push the command
- COM_BufAddText(inputlines[inputline]+1);
+ COM_BufAddText(inputlines[inputline]);
COM_BufAddText("\n");
- CONS_Printf("%s\n", inputlines[inputline]);
+ CONS_Printf("\x86""%c""\x80""%s\n", CON_PROMPTCHAR, inputlines[inputline]);
inputline = (inputline+1) & 31;
inputhist = inputline;
-
- memset(inputlines[inputline], 0, CON_MAXPROMPTCHARS);
- inputlines[inputline][0] = CON_PROMPTCHAR;
- input_cx = 1;
+ CON_InputClear();
return true;
}
- // backspace command prompt
- if (key == KEY_BACKSPACE)
+ // backspace and delete command prompt
+ if (input_sel != input_cur)
{
- if (input_cx > 1)
+ if (key == KEY_BACKSPACE || key == KEY_DEL)
{
- input_cx--;
- inputlines[inputline][input_cx] = 0;
+ CON_InputDelSelection();
+ return true;
}
+ }
+ else if (key == KEY_BACKSPACE)
+ {
+ CON_InputDelChar();
+ return true;
+ }
+ else if (key == KEY_DEL)
+ {
+ if (input_cur == input_len)
+ return true;
+ ++input_cur;
+ CON_InputDelChar();
return true;
}
@@ -888,18 +999,15 @@ boolean CON_Responder(event_t *ev)
{
// copy one of the previous inputlines to the current
do
- {
inputhist = (inputhist - 1) & 31; // cycle back
- } while (inputhist != inputline && !inputlines[inputhist][1]);
+ while (inputhist != inputline && !inputlines[inputhist][0]);
// stop at the last history input line, which is the
// current line + 1 because we cycle through the 32 input lines
if (inputhist == inputline)
inputhist = (inputline + 1) & 31;
- M_Memcpy(inputlines[inputline], inputlines[inputhist], CON_MAXPROMPTCHARS);
- input_cx = strlen(inputlines[inputline]);
-
+ CON_InputSetString(inputlines[inputhist]);
return true;
}
@@ -909,23 +1017,14 @@ boolean CON_Responder(event_t *ev)
if (inputhist == inputline)
return true;
do
- {
inputhist = (inputhist + 1) & 31;
- } while (inputhist != inputline && !inputlines[inputhist][1]);
-
- memset(inputlines[inputline], 0, CON_MAXPROMPTCHARS);
+ while (inputhist != inputline && !inputlines[inputhist][0]);
// back to currentline
if (inputhist == inputline)
- {
- inputlines[inputline][0] = CON_PROMPTCHAR;
- input_cx = 1;
- }
+ CON_InputClear();
else
- {
- strcpy(inputlines[inputline], inputlines[inputhist]);
- input_cx = strlen(inputlines[inputline]);
- }
+ CON_InputSetString(inputlines[inputhist]);
return true;
}
@@ -950,15 +1049,12 @@ boolean CON_Responder(event_t *ev)
return false;
// add key to cmd line here
- if (input_cx < CON_MAXPROMPTCHARS)
- {
- if (key >= 'A' && key <= 'Z' && !shiftdown) //this is only really necessary for dedicated servers
- key = key + 'a' - 'A';
+ if (key >= 'A' && key <= 'Z' && !shiftdown) //this is only really necessary for dedicated servers
+ key = key + 'a' - 'A';
- inputlines[inputline][input_cx] = (char)key;
- inputlines[inputline][input_cx + 1] = 0;
- input_cx++;
- }
+ if (input_sel != input_cur)
+ CON_InputDelSelection();
+ CON_InputAddChar(key);
return true;
}
@@ -1242,26 +1338,89 @@ void CONS_Error(const char *msg)
//
static void CON_DrawInput(void)
{
- char *p;
- size_t c;
- INT32 x, y;
INT32 charwidth = (INT32)con_scalefactor << 3;
-
- // input line scrolls left if it gets too long
- p = inputlines[inputline];
- if (input_cx >= con_width-11)
- p += input_cx - (con_width-11) + 1;
+ const char *p = inputlines[inputline];
+ size_t c, clen, cend;
+ UINT8 lellip = 0, rellip = 0;
+ INT32 x, y, i;
y = con_curlines - 12 * con_scalefactor;
+ x = charwidth*2;
- for (c = 0, x = charwidth; c < con_width-11; c++, x += charwidth)
- V_DrawCharacter(x, y, p[c] | cv_constextsize.value | V_NOSCALESTART, !cv_allcaps.value);
+ clen = con_width-13;
- // draw the blinking cursor
- //
- x = ((input_cx >= con_width-11) ? (INT32)(con_width-11) : (INT32)((input_cx + 1)) * charwidth);
- if (con_tick < 4)
- V_DrawCharacter(x, y, '_' | cv_constextsize.value | V_NOSCALESTART, !cv_allcaps.value);
+ if (input_len <= clen)
+ {
+ c = 0;
+ clen = input_len;
+ }
+ else // input line scrolls left if it gets too long
+ {
+ clen -= 2; // There will always be some extra truncation -- but where is what we'll find out
+
+ if (input_cur <= clen/2)
+ {
+ // Close enough to right edge to show all
+ c = 0;
+ // Always will truncate right side from this position, so always draw right ellipsis
+ rellip = 1;
+ }
+ else
+ {
+ // Cursor in the middle (or right side) of input
+ // Move over for the ellipsis
+ c = input_cur - (clen/2) + 2;
+ x += charwidth*2;
+ lellip = 1;
+
+ if (c + clen >= input_len)
+ {
+ // Cursor in the right side of input
+ // We were too far over, so move back
+ c = input_len - clen;
+ }
+ else
+ {
+ // Cursor in the middle -- ellipses on both sides
+ clen -= 2;
+ rellip = 1;
+ }
+ }
+ }
+
+ if (lellip)
+ {
+ x -= charwidth*3;
+ if (input_sel < c)
+ V_DrawFill(x, y, charwidth*3, (10 * con_scalefactor), 77 | V_NOSCALESTART);
+ for (i = 0; i < 3; ++i, x += charwidth)
+ V_DrawCharacter(x, y, '.' | cv_constextsize.value | V_GRAYMAP | V_NOSCALESTART, true);
+ }
+ else
+ V_DrawCharacter(x-charwidth, y, CON_PROMPTCHAR | cv_constextsize.value | V_GRAYMAP | V_NOSCALESTART, true);
+
+ for (cend = c + clen; c < cend; ++c, x += charwidth)
+ {
+ if ((input_sel > c && input_cur <= c) || (input_sel <= c && input_cur > c))
+ {
+ V_DrawFill(x, y, charwidth, (10 * con_scalefactor), 77 | V_NOSCALESTART);
+ V_DrawCharacter(x, y, p[c] | cv_constextsize.value | V_YELLOWMAP | V_NOSCALESTART, true);
+ }
+ else
+ V_DrawCharacter(x, y, p[c] | cv_constextsize.value | V_NOSCALESTART, true);
+
+ if (c == input_cur && con_tick >= 4)
+ V_DrawCharacter(x, y + (con_scalefactor*2), '_' | cv_constextsize.value | V_NOSCALESTART, true);
+ }
+ if (cend == input_cur && con_tick >= 4)
+ V_DrawCharacter(x, y + (con_scalefactor*2), '_' | cv_constextsize.value | V_NOSCALESTART, true);
+ if (rellip)
+ {
+ if (input_sel > cend)
+ V_DrawFill(x, y, charwidth*3, (10 * con_scalefactor), 77 | V_NOSCALESTART);
+ for (i = 0; i < 3; ++i, x += charwidth)
+ V_DrawCharacter(x, y, '.' | cv_constextsize.value | V_GRAYMAP | V_NOSCALESTART, true);
+ }
}
// draw the last lines of console text to the top of the screen
@@ -1306,11 +1465,11 @@ static void CON_DrawHudlines(void)
else
{
//charwidth = SHORT(hu_font['A'-HU_FONTSTART]->width) * con_scalefactor;
- V_DrawCharacter(x, y, (INT32)(*p) | charflags | cv_constextsize.value | V_NOSCALESTART, !cv_allcaps.value);
+ V_DrawCharacter(x, y, (INT32)(*p) | charflags | cv_constextsize.value | V_NOSCALESTART, true);
}
}
- //V_DrawCharacter(x, y, (p[c]&0xff) | cv_constextsize.value | V_NOSCALESTART, !cv_allcaps.value);
+ //V_DrawCharacter(x, y, (p[c]&0xff) | cv_constextsize.value | V_NOSCALESTART, true);
y += charheight;
}
@@ -1417,7 +1576,7 @@ static void CON_DrawConsole(void)
{
// inu: no more width (was always 0 and vid.width)
if (rendermode != render_none)
- V_DrawFadeConsBack(con_curlines, cons_backcolor.value); // translucent background
+ V_DrawFadeConsBack(con_curlines); // translucent background
}
// draw console text lines from top to bottom
@@ -1448,7 +1607,7 @@ static void CON_DrawConsole(void)
charflags = (*p & 0x7f) << V_CHARCOLORSHIFT;
p++;
}
- V_DrawCharacter(x, y, (INT32)(*p) | charflags | cv_constextsize.value | V_NOSCALESTART, !cv_allcaps.value);
+ V_DrawCharacter(x, y, (INT32)(*p) | charflags | cv_constextsize.value | V_NOSCALESTART, true);
}
}
diff --git a/src/console.h b/src/console.h
index 47af65e21..8cf6483ff 100644
--- a/src/console.h
+++ b/src/console.h
@@ -40,11 +40,10 @@ extern consvar_t cons_backcolor;
extern UINT8 *yellowmap, *purplemap, *lgreenmap, *bluemap, *graymap, *redmap, *orangemap;
-// Console bg colors:
-extern UINT8 *cwhitemap, *corangemap, *cbluemap, *cgreenmap, *cgraymap,
- *credmap;
+// Console bg color (auto updated to match)
+extern UINT8 *consolebgmap;
-void CON_ReSetupBackColormap(UINT16 num);
+void CON_SetupBackColormap(void);
void CON_ClearHUD(void); // clear heads up messages
void CON_Ticker(void);
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 734173f8d..decc2e74b 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -58,28 +58,35 @@
// NETWORKING
//
// gametic is the tic about to (or currently being) run
-// maketic is the tic that hasn't had control made for it yet
-// server:
+// Server:
+// maketic is the tic that hasn't had control made for it yet
// nettics is the tic for each node
// firstticstosend is the lowest value of nettics
-// client:
-// neededtic is the tic needed by the client for run the game
+// Client:
+// neededtic is the tic needed by the client to run the game
// firstticstosend is used to optimize a condition
-// normally maketic >= gametic > 0
+// Normally maketic >= gametic > 0
#define PREDICTIONQUEUE BACKUPTICS
#define PREDICTIONMASK (PREDICTIONQUEUE-1)
#define MAX_REASONLENGTH 30
boolean server = true; // true or false but !server == client
+#define client (!server)
boolean nodownload = false;
static boolean serverrunning = false;
INT32 serverplayer = 0;
char motd[254], server_context[8]; // Message of the Day, Unique Context (even without Mumble support)
-// server specific vars
+// Server specific vars
UINT8 playernode[MAXPLAYERS];
+// Minimum timeout for sending the savegame
+// The actual timeout will be longer depending on the savegame length
+tic_t jointimeout = (10*TICRATE);
+static boolean sendingsavegame[MAXNETNODES]; // Are we sending the savegame?
+static tic_t freezetimeout[MAXNETNODES]; // Until when can this node freeze the server before getting a timeout?
+
#ifdef NEWPING
UINT16 pingmeasurecount = 1;
UINT32 realpingtable[MAXPLAYERS]; //the base table of ping where an average will be sent to everyone.
@@ -108,7 +115,7 @@ static UINT8 resynch_local_inprogress = false; // WE are desynched and getting p
static UINT8 player_joining = false;
UINT8 hu_resynching = 0;
-// client specific
+// Client specific
static ticcmd_t localcmds;
static ticcmd_t localcmds2;
static boolean cl_packetmissed;
@@ -151,12 +158,6 @@ static consvar_t cv_showjoinaddress = {"showjoinaddress", "On", 0, CV_OnOff, NUL
static CV_PossibleValue_t playbackspeed_cons_t[] = {{1, "MIN"}, {10, "MAX"}, {0, NULL}};
consvar_t cv_playbackspeed = {"playbackspeed", "1", 0, playbackspeed_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-void D_ResetTiccmds(void)
-{
- memset(&localcmds, 0, sizeof(ticcmd_t));
- memset(&localcmds2, 0, sizeof(ticcmd_t));
-}
-
static inline void *G_DcpyTiccmd(void* dest, const ticcmd_t* src, const size_t n)
{
const size_t d = n / sizeof(ticcmd_t);
@@ -185,11 +186,17 @@ static inline void *G_ScpyTiccmd(ticcmd_t* dest, void* src, const size_t n)
-// some software don't support largest packet
-// (original sersetup, not exactely, but the probabylity of sending a packet
-// of 512 octet is like 0.1)
+// Some software don't support largest packet
+// (original sersetup, not exactely, but the probability of sending a packet
+// of 512 bytes is like 0.1)
UINT16 software_MAXPACKETLENGTH;
+/** Guesses the value of a tic from its lowest byte and from maketic
+ *
+ * \param low The lowest byte of the tic value
+ * \return The full tic value
+ *
+ */
tic_t ExpandTics(INT32 low)
{
INT32 delta;
@@ -214,7 +221,7 @@ void RegisterNetXCmd(netxcmd_t id, void (*cmd_f)(UINT8 **p, INT32 playernum))
{
#ifdef PARANOIA
if (id >= MAXNETXCMD)
- I_Error("command id %d too big", id);
+ I_Error("Command id %d too big", id);
if (listnetxcmd[id] != 0)
I_Error("Command id %d already used", id);
#endif
@@ -378,7 +385,7 @@ static void ExtraDataTicker(void)
{
const UINT8 id = *curpos;
curpos++;
- DEBFILE(va("executing x_cmd %u ply %u ", id, i));
+ DEBFILE(va("executing x_cmd %s ply %u ", netxcmdnames[id - 1], i));
(listnetxcmd[id])(&curpos, i);
DEBFILE("done\n");
}
@@ -401,7 +408,11 @@ static void ExtraDataTicker(void)
}
}
- D_FreeTextcmd(gametic);
+ // If you are a client, you can safely forget the net commands for this tic
+ // If you are the server, you need to remember them until every client has been aknowledged,
+ // because if you need to resend a PT_SERVERTICS packet, you need to put the commands in it
+ if (client)
+ D_FreeTextcmd(gametic);
}
static void D_Clearticcmd(tic_t tic)
@@ -416,6 +427,19 @@ static void D_Clearticcmd(tic_t tic)
DEBFILE(va("clear tic %5u (%2u)\n", tic, tic%BACKUPTICS));
}
+void D_ResetTiccmds(void)
+{
+ INT32 i;
+
+ memset(&localcmds, 0, sizeof(ticcmd_t));
+ memset(&localcmds2, 0, sizeof(ticcmd_t));
+
+ // Reset the net command list
+ for (i = 0; i < TEXTCMD_HASH_SIZE; i++)
+ while (textcmds[i])
+ D_Clearticcmd(textcmds[i]->tic);
+}
+
// -----------------------------------------------------------------
// end of extra data function
// -----------------------------------------------------------------
@@ -495,7 +519,7 @@ static inline void resynch_write_player(resynch_pak *rsp, const size_t i)
rsp->powers[j] = (UINT16)SHORT(players[i].powers[j]);
// Score is resynched in the rspfirm resync packet
- rsp->health = 0; // resynched with mo health
+ rsp->rings = LONG(players[i].rings);
rsp->lives = players[i].lives;
rsp->continues = players[i].continues;
rsp->scoreadd = players[i].scoreadd;
@@ -504,6 +528,7 @@ static inline void resynch_write_player(resynch_pak *rsp, const size_t i)
rsp->skincolor = players[i].skincolor;
rsp->skin = LONG(players[i].skin);
+ rsp->availabilities = LONG(players[i].availabilities);
// Just in case Lua does something like
// modify these at runtime
rsp->camerascale = (fixed_t)LONG(players[i].camerascale);
@@ -527,7 +552,6 @@ static inline void resynch_write_player(resynch_pak *rsp, const size_t i)
rsp->playerspinheight = (fixed_t)LONG(players[i].spinheight);
rsp->speed = (fixed_t)LONG(players[i].speed);
- rsp->jumping = players[i].jumping;
rsp->secondjump = players[i].secondjump;
rsp->fly1 = players[i].fly1;
rsp->glidetime = (tic_t)LONG(players[i].glidetime);
@@ -582,7 +606,6 @@ static inline void resynch_write_player(resynch_pak *rsp, const size_t i)
rsp->hasmo = true;
rsp->health = LONG(players[i].mo->health);
-
rsp->angle = (angle_t)LONG(players[i].mo->angle);
rsp->x = LONG(players[i].mo->x);
rsp->y = LONG(players[i].mo->y);
@@ -625,7 +648,7 @@ static void resynch_read_player(resynch_pak *rsp)
players[i].powers[j] = (UINT16)SHORT(rsp->powers[j]);
// Score is resynched in the rspfirm resync packet
- players[i].health = rsp->health;
+ players[i].rings = LONG(rsp->rings);
players[i].lives = rsp->lives;
players[i].continues = rsp->continues;
players[i].scoreadd = rsp->scoreadd;
@@ -634,6 +657,7 @@ static void resynch_read_player(resynch_pak *rsp)
players[i].skincolor = rsp->skincolor;
players[i].skin = LONG(rsp->skin);
+ players[i].availabilities = LONG(rsp->availabilities);
// Just in case Lua does something like
// modify these at runtime
players[i].camerascale = (fixed_t)LONG(rsp->camerascale);
@@ -657,7 +681,6 @@ static void resynch_read_player(resynch_pak *rsp)
players[i].spinheight = (fixed_t)LONG(rsp->playerspinheight);
players[i].speed = (fixed_t)LONG(rsp->speed);
- players[i].jumping = rsp->jumping;
players[i].secondjump = rsp->secondjump;
players[i].fly1 = rsp->fly1;
players[i].glidetime = (tic_t)LONG(rsp->glidetime);
@@ -859,12 +882,13 @@ static inline void resynch_write_others(resynchend_pak *rst)
{
UINT8 i;
- rst->ingame = rst->ctfteam = 0;
+ rst->ingame = 0;
for (i = 0; i < MAXPLAYERS; ++i)
{
if (!playeringame[i])
{
+ rst->ctfteam[i] = 0;
rst->score[i] = 0;
rst->numboxes[i] = 0;
rst->totalring[i] = 0;
@@ -874,11 +898,8 @@ static inline void resynch_write_others(resynchend_pak *rst)
}
if (!players[i].spectator)
- {
rst->ingame |= (1< 1)
- rst->ctfteam |= (1<ctfteam[i] = (INT32)LONG(players[i].ctfteam);
rst->score[i] = (UINT32)LONG(players[i].score);
rst->numboxes[i] = SHORT(players[i].numboxes);
rst->totalring[i] = SHORT(players[i].totalring);
@@ -888,28 +909,18 @@ static inline void resynch_write_others(resynchend_pak *rst)
// endian safeness
rst->ingame = (UINT32)LONG(rst->ingame);
- rst->ctfteam = (UINT32)LONG(rst->ctfteam);
}
static inline void resynch_read_others(resynchend_pak *p)
{
UINT8 i;
UINT32 loc_ingame = (UINT32)LONG(p->ingame);
- UINT32 loc_ctfteam = (UINT32)LONG(p->ctfteam);
for (i = 0; i < MAXPLAYERS; ++i)
{
// We don't care if they're in the game or not, just write all the data.
- if (loc_ingame & (1<ctfteam[i]); // no, 0 does not mean spectator, at least not in Match
players[i].score = (UINT32)LONG(p->score[i]);
players[i].numboxes = SHORT(p->numboxes[i]);
players[i].totalring = SHORT(p->totalring[i]);
@@ -1029,6 +1040,9 @@ static void SV_AcknowledgeResynchAck(INT32 node, UINT8 rsg)
resynch_status[node] &= ~(1<>10));
- V_DrawRightAlignedString(BASEVIDWIDTH/2+128, BASEVIDHEIGHT-24, V_20TRANS|V_MONOSPACE,
- va("%3.1fK/s ", ((double)getbps)/1024));
+ case CL_DOWNLOADSAVEGAME:
+ if (lastfilenum != -1)
+ {
+ cltext = M_GetText("Downloading game state...");
+ Net_GetNetStat();
+ V_DrawString(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, V_20TRANS|V_MONOSPACE,
+ va(" %4uK",fileneeded[lastfilenum].currentsize>>10));
+ V_DrawRightAlignedString(BASEVIDWIDTH/2+128, BASEVIDHEIGHT-24, V_20TRANS|V_MONOSPACE,
+ va("%3.1fK/s ", ((double)getbps)/1024));
+ }
+ else
+ cltext = M_GetText("Waiting to download game state...");
break;
#endif
- case cl_askjoin:
- case cl_waitjoinresponse:
+ case CL_ASKJOIN:
+ case CL_WAITJOINRESPONSE:
cltext = M_GetText("Requesting to join...");
break;
default:
@@ -1142,34 +1161,45 @@ static inline void CL_DrawConnectionStatus(void)
}
else
{
- INT32 dldlength;
- static char tempname[32];
+ if (lastfilenum != -1)
+ {
+ INT32 dldlength;
+ static char tempname[32];
- Net_GetNetStat();
- dldlength = (INT32)((fileneeded[lastfilenum].currentsize/(double)fileneeded[lastfilenum].totalsize) * 256);
- if (dldlength > 256)
- dldlength = 256;
- V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, 256, 8, 111);
- V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, dldlength, 8, 96);
+ Net_GetNetStat();
+ dldlength = (INT32)((fileneeded[lastfilenum].currentsize/(double)fileneeded[lastfilenum].totalsize) * 256);
+ if (dldlength > 256)
+ dldlength = 256;
+ V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, 256, 8, 111);
+ V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, dldlength, 8, 96);
- memset(tempname, 0, sizeof(tempname));
- nameonly(strncpy(tempname, fileneeded[lastfilenum].filename, 31));
+ memset(tempname, 0, sizeof(tempname));
+ nameonly(strncpy(tempname, fileneeded[lastfilenum].filename, 31));
- V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-24-32, V_YELLOWMAP,
- va(M_GetText("Downloading \"%s\""), tempname));
- V_DrawString(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, V_20TRANS|V_MONOSPACE,
- va(" %4uK/%4uK",fileneeded[lastfilenum].currentsize>>10,fileneeded[lastfilenum].totalsize>>10));
- V_DrawRightAlignedString(BASEVIDWIDTH/2+128, BASEVIDHEIGHT-24, V_20TRANS|V_MONOSPACE,
- va("%3.1fK/s ", ((double)getbps)/1024));
+ V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-24-32, V_YELLOWMAP,
+ va(M_GetText("Downloading \"%s\""), tempname));
+ V_DrawString(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, V_20TRANS|V_MONOSPACE,
+ va(" %4uK/%4uK",fileneeded[lastfilenum].currentsize>>10,fileneeded[lastfilenum].totalsize>>10));
+ V_DrawRightAlignedString(BASEVIDWIDTH/2+128, BASEVIDHEIGHT-24, V_20TRANS|V_MONOSPACE,
+ va("%3.1fK/s ", ((double)getbps)/1024));
+ }
+ else
+ V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-24-32, V_YELLOWMAP,
+ M_GetText("Waiting to download files..."));
}
}
#endif
-//
-// CL_SendJoin
-//
-// send a special packet for declare how many player in local
-// used only in arbitratrenetstart()
+/** Sends a special packet to declare how many players in local
+ * Used only in arbitratrenetstart()
+ * Sends a PT_CLIENTJOIN packet to the server
+ *
+ * \return True if the packet was successfully sent
+ * \todo Improve the description...
+ * Because to be honest, I have no idea what arbitratrenetstart is...
+ * Is it even used...?
+ *
+ */
static boolean CL_SendJoin(void)
{
UINT8 localplayers = 1;
@@ -1304,6 +1334,12 @@ static void SV_SendPlayerInfo(INT32 node)
HSendPacket(node, false, 0, sizeof(plrinfo) * MAXPLAYERS);
}
+/** Sends a PT_SERVERCFG packet
+ *
+ * \param node The destination
+ * \return True if the packet was successfully sent
+ *
+ */
static boolean SV_SendServerConfig(INT32 node)
{
INT32 i;
@@ -1328,6 +1364,7 @@ static boolean SV_SendServerConfig(INT32 node)
// which is nice and easy for us to detect
memset(netbuffer->u.servercfg.playerskins, 0xFF, sizeof(netbuffer->u.servercfg.playerskins));
memset(netbuffer->u.servercfg.playercolor, 0xFF, sizeof(netbuffer->u.servercfg.playercolor));
+ memset(netbuffer->u.servercfg.playeravailabilities, 0xFF, sizeof(netbuffer->u.servercfg.playeravailabilities));
for (i = 0; i < MAXPLAYERS; i++)
{
@@ -1335,6 +1372,7 @@ static boolean SV_SendServerConfig(INT32 node)
continue;
netbuffer->u.servercfg.playerskins[i] = (UINT8)players[i].skin;
netbuffer->u.servercfg.playercolor[i] = (UINT8)players[i].skincolor;
+ netbuffer->u.servercfg.playeravailabilities[i] = (UINT32)LONG(players[i].availabilities);
}
memcpy(netbuffer->u.servercfg.server_context, server_context, 8);
@@ -1436,8 +1474,12 @@ static void SV_SendSaveGame(INT32 node)
WRITEUINT32(savebuffer, 0);
}
- SendRam(node, buffertosend, length, SF_RAM, 0);
+ SV_SendRam(node, buffertosend, length, SF_RAM, 0);
save_p = NULL;
+
+ // Remember when we started sending the savegame so we can handle timeouts
+ sendingsavegame[node] = true;
+ freezetimeout[node] = I_GetTime() + jointimeout + length / 1024; // 1 extra tic for each kilobyte
}
#ifdef DUMPCONSISTENCY
@@ -1521,8 +1563,6 @@ static void CL_LoadReceivedSavegame(void)
automapactive = false;
// load a base level
- playerdeadview = false;
-
if (P_LoadNetGame())
{
const INT32 actnum = mapheaderinfo[gamemap-1]->actnum;
@@ -1531,7 +1571,7 @@ static void CL_LoadReceivedSavegame(void)
{
CONS_Printf(": %s", mapheaderinfo[gamemap-1]->lvlttl);
if (!(mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE))
- CONS_Printf(M_GetText("ZONE"));
+ CONS_Printf(M_GetText(" ZONE"));
if (actnum > 0)
CONS_Printf(" %2d", actnum);
}
@@ -1687,11 +1727,263 @@ void CL_UpdateServerList(boolean internetsearch, INT32 room)
#endif // ifndef NONET
-// use adaptive send using net_bandwidth and stat.sendbytes
+/** Called by CL_ServerConnectionTicker
+ *
+ * \param viams ???
+ * \param asksent ???
+ * \return False if the connection was aborted
+ * \sa CL_ServerConnectionTicker
+ * \sa CL_ConnectToServer
+ *
+ */
+static boolean CL_ServerConnectionSearchTicker(boolean viams, tic_t *asksent)
+{
+#ifndef NONET
+ INT32 i;
+
+ // serverlist is updated by GetPacket function
+ if (serverlistcount > 0)
+ {
+ // this can be a responce to our broadcast request
+ if (servernode == -1 || servernode >= MAXNETNODES)
+ {
+ i = 0;
+ servernode = serverlist[i].node;
+ CONS_Printf(M_GetText("Found, "));
+ }
+ else
+ {
+ i = SL_SearchServer(servernode);
+ if (i < 0)
+ return true;
+ }
+
+ // Quit here rather than downloading files and being refused later.
+ if (serverlist[i].info.numberofplayer >= serverlist[i].info.maxplayer)
+ {
+ D_QuitNetGame();
+ CL_Reset();
+ D_StartTitle();
+ M_StartMessage(va(M_GetText("Maximum players reached: %d\n\nPress ESC\n"), serverlist[i].info.maxplayer), NULL, MM_NOTHING);
+ return false;
+ }
+
+ if (client)
+ {
+ D_ParseFileneeded(serverlist[i].info.fileneedednum,
+ serverlist[i].info.fileneeded);
+ CONS_Printf(M_GetText("Checking files...\n"));
+ i = CL_CheckFiles();
+ if (i == 3) // too many files
+ {
+ D_QuitNetGame();
+ CL_Reset();
+ D_StartTitle();
+ M_StartMessage(M_GetText(
+ "You have too many WAD files loaded\n"
+ "to add ones the server is using.\n"
+ "Please restart SRB2 before connecting.\n\n"
+ "Press ESC\n"
+ ), NULL, MM_NOTHING);
+ return false;
+ }
+ else if (i == 2) // cannot join for some reason
+ {
+ D_QuitNetGame();
+ CL_Reset();
+ D_StartTitle();
+ M_StartMessage(M_GetText(
+ "You have WAD files loaded or have\n"
+ "modified the game in some way, and\n"
+ "your file list does not match\n"
+ "the server's file list.\n"
+ "Please restart SRB2 before connecting.\n\n"
+ "Press ESC\n"
+ ), NULL, MM_NOTHING);
+ return false;
+ }
+ else if (i == 1)
+ cl_mode = CL_ASKJOIN;
+ else
+ {
+ // must download something
+ // can we, though?
+ if (!CL_CheckDownloadable()) // nope!
+ {
+ D_QuitNetGame();
+ CL_Reset();
+ D_StartTitle();
+ M_StartMessage(M_GetText(
+ "You cannot connect to this server\n"
+ "because you cannot download the files\n"
+ "that you are missing from the server.\n\n"
+ "See the console or log file for\n"
+ "more details.\n\n"
+ "Press ESC\n"
+ ), NULL, MM_NOTHING);
+ return false;
+ }
+ // no problem if can't send packet, we will retry later
+ if (CL_SendRequestFile())
+ cl_mode = CL_DOWNLOADFILES;
+ }
+ }
+ else
+ cl_mode = CL_ASKJOIN; // files need not be checked for the server.
+
+ return true;
+ }
+
+ // Ask the info to the server (askinfo packet)
+ if (*asksent + NEWTICRATE < I_GetTime())
+ {
+ SendAskInfo(servernode, viams);
+ *asksent = I_GetTime();
+ }
+#else
+ (void)viams;
+ (void)asksent;
+ // No netgames, so we skip this state.
+ cl_mode = CL_ASKJOIN;
+#endif // ifndef NONET/else
+
+ return true;
+}
+
+/** Called by CL_ConnectToServer
+ *
+ * \param viams ???
+ * \param tmpsave The name of the gamestate file???
+ * \param oldtic Used for knowing when to poll events and redraw
+ * \param asksent ???
+ * \return False if the connection was aborted
+ * \sa CL_ServerConnectionSearchTicker
+ * \sa CL_ConnectToServer
+ *
+ */
+static boolean CL_ServerConnectionTicker(boolean viams, const char *tmpsave, tic_t *oldtic, tic_t *asksent)
+{
+ boolean waitmore;
+ INT32 i;
+
+#ifdef NONET
+ (void)tmpsave;
+#endif
+
+ switch (cl_mode)
+ {
+ case CL_SEARCHING:
+ if (!CL_ServerConnectionSearchTicker(viams, asksent))
+ return false;
+ break;
+
+ case CL_DOWNLOADFILES:
+ waitmore = false;
+ for (i = 0; i < fileneedednum; i++)
+ if (fileneeded[i].status == FS_DOWNLOADING
+ || fileneeded[i].status == FS_REQUESTED)
+ {
+ waitmore = true;
+ break;
+ }
+ if (waitmore)
+ break; // exit the case
+
+ cl_mode = CL_ASKJOIN; // don't break case continue to cljoin request now
+
+ case CL_ASKJOIN:
+ CL_LoadServerFiles();
+#ifdef JOININGAME
+ // prepare structures to save the file
+ // WARNING: this can be useless in case of server not in GS_LEVEL
+ // but since the network layer doesn't provide ordered packets...
+ CL_PrepareDownloadSaveGame(tmpsave);
+#endif
+ if (CL_SendJoin())
+ cl_mode = CL_WAITJOINRESPONSE;
+ break;
+
+#ifdef JOININGAME
+ case CL_DOWNLOADSAVEGAME:
+ // At this state, the first (and only) needed file is the gamestate
+ if (fileneeded[0].status == FS_FOUND)
+ {
+ // Gamestate is now handled within CL_LoadReceivedSavegame()
+ CL_LoadReceivedSavegame();
+ cl_mode = CL_CONNECTED;
+ } // don't break case continue to CL_CONNECTED
+ else
+ break;
+#endif
+
+ case CL_WAITJOINRESPONSE:
+ case CL_CONNECTED:
+ default:
+ break;
+
+ // Connection closed by cancel, timeout or refusal.
+ case CL_ABORTED:
+ cl_mode = CL_SEARCHING;
+ return false;
+
+ }
+
+ GetPackets();
+ Net_AckTicker();
+
+ // Call it only once by tic
+ if (*oldtic != I_GetTime())
+ {
+ INT32 key;
+
+ I_OsPolling();
+ key = I_GetKey();
+ if (key == KEY_ESCAPE)
+ {
+ CONS_Printf(M_GetText("Network game synchronization aborted.\n"));
+// M_StartMessage(M_GetText("Network game synchronization aborted.\n\nPress ESC\n"), NULL, MM_NOTHING);
+ D_QuitNetGame();
+ CL_Reset();
+ D_StartTitle();
+ return false;
+ }
+
+ // why are these here? this is for servers, we're a client
+ //if (key == 's' && server)
+ // doomcom->numnodes = (INT16)pnumnodes;
+ //SV_FileSendTicker();
+ *oldtic = I_GetTime();
+
+#ifdef CLIENT_LOADINGSCREEN
+ if (client && cl_mode != CL_CONNECTED && cl_mode != CL_ABORTED)
+ {
+ F_TitleScreenTicker(true);
+ F_TitleScreenDrawer();
+ CL_DrawConnectionStatus();
+ I_UpdateNoVsync(); // page flip or blit buffer
+ if (moviemode)
+ M_SaveFrame();
+ }
+#else
+ CON_Drawer();
+ I_UpdateNoVsync();
+#endif
+ }
+ else
+ I_Sleep();
+
+ return true;
+}
+
+/** Use adaptive send using net_bandwidth and stat.sendbytes
+ *
+ * \param viams ???
+ * \todo Better description...
+ *
+ */
static void CL_ConnectToServer(boolean viams)
{
INT32 pnumnodes, nodewaited = doomcom->numnodes, i;
- boolean waitmore;
tic_t oldtic;
#ifndef NONET
tic_t asksent;
@@ -1702,14 +1994,14 @@ static void CL_ConnectToServer(boolean viams)
sprintf(tmpsave, "%s" PATHSEP TMPSAVENAME, srb2home);
#endif
- cl_mode = cl_searching;
+ cl_mode = CL_SEARCHING;
#ifdef CLIENT_LOADINGSCREEN
- lastfilenum = 0;
+ lastfilenum = -1;
#endif
#ifdef JOININGAME
- // don't get a corrupt savegame error because tmpsave already exists
+ // Don't get a corrupt savegame error because tmpsave already exists
if (FIL_FileExists(tmpsave) && unlink(tmpsave) == -1)
I_Error("Can't delete %s\n", tmpsave);
#endif
@@ -1733,7 +2025,7 @@ static void CL_ConnectToServer(boolean viams)
pnumnodes = 1;
oldtic = I_GetTime() - 1;
#ifndef NONET
- asksent = (tic_t)-TICRATE;
+ asksent = (tic_t) - TICRATE;
i = SL_SearchServer(servernode);
@@ -1760,197 +2052,23 @@ static void CL_ConnectToServer(boolean viams)
do
{
- switch (cl_mode)
- {
- case cl_searching:
+ // If the connection was aborted for some reason, leave
#ifndef NONET
- // serverlist is updated by GetPacket function
- if (serverlistcount > 0)
- {
- // this can be a responce to our broadcast request
- if (servernode == -1 || servernode >= MAXNETNODES)
- {
- i = 0;
- servernode = serverlist[i].node;
- CONS_Printf(M_GetText("Found, "));
- }
- else
- {
- i = SL_SearchServer(servernode);
- if (i < 0)
- break; // the case
- }
-
- // Quit here rather than downloading files and being refused later.
- if (serverlist[i].info.numberofplayer >= serverlist[i].info.maxplayer)
- {
- D_QuitNetGame();
- CL_Reset();
- D_StartTitle();
- M_StartMessage(va(M_GetText("Maximum players reached: %d\n\nPress ESC\n"), serverlist[i].info.maxplayer), NULL, MM_NOTHING);
- return;
- }
-
- if (!server)
- {
- D_ParseFileneeded(serverlist[i].info.fileneedednum,
- serverlist[i].info.fileneeded);
- CONS_Printf(M_GetText("Checking files...\n"));
- i = CL_CheckFiles();
- if (i == 2) // cannot join for some reason
- {
- D_QuitNetGame();
- CL_Reset();
- D_StartTitle();
- M_StartMessage(M_GetText(
- "You have WAD files loaded or have\n"
- "modified the game in some way, and\n"
- "your file list does not match\n"
- "the server's file list.\n"
- "Please restart SRB2 before connecting.\n\n"
- "Press ESC\n"
- ), NULL, MM_NOTHING);
- return;
- }
- else if (i == 1)
- cl_mode = cl_askjoin;
- else
- {
- // must download something
- // can we, though?
- if (!CL_CheckDownloadable()) // nope!
- {
- D_QuitNetGame();
- CL_Reset();
- D_StartTitle();
- M_StartMessage(M_GetText(
- "You cannot conect to this server\n"
- "because you cannot download the files\n"
- "that you are missing from the server.\n\n"
- "See the console or log file for\n"
- "more details.\n\n"
- "Press ESC\n"
- ), NULL, MM_NOTHING);
- return;
- }
- // no problem if can't send packet, we will retry later
- if (CL_SendRequestFile())
- cl_mode = cl_downloadfiles;
- }
- }
- else
- cl_mode = cl_askjoin; // files need not be checked for the server.
- break;
- }
- // ask the info to the server (askinfo packet)
- if (asksent + NEWTICRATE < I_GetTime())
- {
- SendAskInfo(servernode, viams);
- asksent = I_GetTime();
- }
+ if (!CL_ServerConnectionTicker(viams, tmpsave, &oldtic, &asksent))
#else
- (void)viams;
- // No netgames, so we skip this state.
- cl_mode = cl_askjoin;
-#endif // ifndef NONET/else
- break;
- case cl_downloadfiles:
- waitmore = false;
- for (i = 0; i < fileneedednum; i++)
- if (fileneeded[i].status == FS_DOWNLOADING
- || fileneeded[i].status == FS_REQUESTED)
- {
- waitmore = true;
- break;
- }
- if (waitmore)
- break; // exit the case
-
- cl_mode = cl_askjoin; // don't break case continue to cljoin request now
- case cl_askjoin:
- CL_LoadServerFiles();
-#ifdef JOININGAME
- // prepare structures to save the file
- // WARNING: this can be useless in case of server not in GS_LEVEL
- // but since the network layer doesn't provide ordered packets...
- CL_PrepareDownloadSaveGame(tmpsave);
+ if (!CL_ServerConnectionTicker(viams, (char*)NULL, &oldtic, (tic_t *)NULL))
#endif
- if (CL_SendJoin())
- cl_mode = cl_waitjoinresponse;
- break;
-#ifdef JOININGAME
- case cl_downloadsavegame:
- if (fileneeded[0].status == FS_FOUND)
- {
- // Gamestate is now handled within CL_LoadReceivedSavegame()
- CL_LoadReceivedSavegame();
- cl_mode = cl_connected;
- } // don't break case continue to cl_connected
- else
- break;
-#endif
- case cl_waitjoinresponse:
- case cl_connected:
- default:
- break;
-
- // Connection closed by cancel, timeout or refusal.
- case cl_aborted:
- cl_mode = cl_searching;
- return;
- }
-
- GetPackets();
- Net_AckTicker();
-
- // call it only one by tic
- if (oldtic != I_GetTime())
- {
- INT32 key;
-
- I_OsPolling();
- key = I_GetKey();
- if (key == KEY_ESCAPE)
- {
- CONS_Printf(M_GetText("Network game synchronization aborted.\n"));
-// M_StartMessage(M_GetText("Network game synchronization aborted.\n\nPress ESC\n"), NULL, MM_NOTHING);
- D_QuitNetGame();
- CL_Reset();
- D_StartTitle();
- return;
- }
-
- // why are these here? this is for servers, we're a client
- //if (key == 's' && server)
- // doomcom->numnodes = (INT16)pnumnodes;
- //FiletxTicker();
- oldtic = I_GetTime();
-
-#ifdef CLIENT_LOADINGSCREEN
- if (!server && cl_mode != cl_connected && cl_mode != cl_aborted)
- {
- F_TitleScreenTicker(true);
- F_TitleScreenDrawer();
- CL_DrawConnectionStatus();
- I_UpdateNoVsync(); // page flip or blit buffer
- if (moviemode)
- M_SaveFrame();
- }
-#else
- CON_Drawer();
- I_UpdateNoVsync();
-#endif
- }
- else I_Sleep();
+ return;
if (server)
{
pnumnodes = 0;
for (i = 0; i < MAXNETNODES; i++)
- if (nodeingame[i]) pnumnodes++;
+ if (nodeingame[i])
+ pnumnodes++;
}
}
- while (!(cl_mode == cl_connected && (!server || (server && nodewaited <= pnumnodes))));
+ while (!(cl_mode == CL_CONNECTED && (client || (server && nodewaited <= pnumnodes))));
DEBFILE(va("Synchronisation Finished\n"));
@@ -2201,13 +2319,7 @@ static void ResetNode(INT32 node);
void CL_ClearPlayer(INT32 playernum)
{
if (players[playernum].mo)
- {
- // Don't leave a NiGHTS ghost!
- if ((players[playernum].pflags & PF_NIGHTSMODE) && players[playernum].mo->tracer)
- P_RemoveMobj(players[playernum].mo->tracer);
P_RemoveMobj(players[playernum].mo);
- }
- players[playernum].mo = NULL;
memset(&players[playernum], 0, sizeof (player_t));
}
@@ -2254,7 +2366,7 @@ static void CL_RemovePlayer(INT32 playernum)
}
count--;
- rings = players[playernum].health - 1;
+ rings = players[playernum].rings;
increment = rings/count;
for (i = 0; i < MAXPLAYERS; i++)
@@ -2415,12 +2527,18 @@ static void Command_Nodes(void)
static void Command_Ban(void)
{
- if (COM_Argc() == 1)
+ if (COM_Argc() < 2)
{
CONS_Printf(M_GetText("Ban : ban and kick a player\n"));
return;
}
+ if (!netgame) // Don't kick Tails in splitscreen!
+ {
+ CONS_Printf(M_GetText("This only works in a netgame.\n"));
+ return;
+ }
+
if (server || adminplayer == consoleplayer)
{
XBOXSTATIC UINT8 buf[3 + MAX_REASONLENGTH];
@@ -2430,9 +2548,10 @@ static void Command_Ban(void)
if (pn == -1 || pn == 0)
return;
- else
- WRITEUINT8(p, pn);
- if (I_Ban && !I_Ban(node))
+
+ WRITEUINT8(p, pn);
+
+ if (server && I_Ban && !I_Ban(node)) // only the server is allowed to do this right now
{
CONS_Alert(CONS_WARNING, M_GetText("Too many bans! Geez, that's a lot of people you're excluding...\n"));
WRITEUINT8(p, KICK_MSG_GO_AWAY);
@@ -2440,7 +2559,8 @@ static void Command_Ban(void)
}
else
{
- Ban_Add(COM_Argv(2));
+ if (server) // only the server is allowed to do this right now
+ Ban_Add(COM_Argv(2));
if (COM_Argc() == 2)
{
@@ -2473,21 +2593,38 @@ static void Command_Ban(void)
static void Command_Kick(void)
{
- XBOXSTATIC UINT8 buf[3 + MAX_REASONLENGTH];
- UINT8 *p = buf;
-
- if (COM_Argc() == 1)
+ if (COM_Argc() < 2)
{
CONS_Printf(M_GetText("kick : kick a player\n"));
return;
}
+ if (!netgame) // Don't kick Tails in splitscreen!
+ {
+ CONS_Printf(M_GetText("This only works in a netgame.\n"));
+ return;
+ }
+
if (server || adminplayer == consoleplayer)
{
+ XBOXSTATIC UINT8 buf[3 + MAX_REASONLENGTH];
+ UINT8 *p = buf;
const SINT8 pn = nametonum(COM_Argv(1));
- WRITESINT8(p, pn);
+
if (pn == -1 || pn == 0)
return;
+
+ // Special case if we are trying to kick a player who is downloading the game state:
+ // trigger a timeout instead of kicking them, because a kick would only
+ // take effect after they have finished downloading
+ if (sendingsavegame[playernode[pn]])
+ {
+ Net_ConnectionTimeout(playernode[pn]);
+ return;
+ }
+
+ WRITESINT8(p, pn);
+
if (COM_Argc() == 2)
{
WRITEUINT8(p, KICK_MSG_GO_AWAY);
@@ -2589,12 +2726,14 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
// If a verified admin banned someone, the server needs to know about it.
// If the playernum isn't zero (the server) then the server needs to record the ban.
- if (server && playernum && msg == KICK_MSG_BANNED)
+ if (server && playernum && (msg == KICK_MSG_BANNED || msg == KICK_MSG_CUSTOM_BAN))
{
if (I_Ban && !I_Ban(playernode[(INT32)pnum]))
- {
CONS_Alert(CONS_WARNING, M_GetText("Too many bans! Geez, that's a lot of people you're excluding...\n"));
- }
+#ifndef NONET
+ else
+ Ban_Add(reason);
+#endif
}
switch (msg)
@@ -2700,7 +2839,12 @@ consvar_t cv_blamecfail = {"blamecfail", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL
// max file size to send to a player (in kilobytes)
static CV_PossibleValue_t maxsend_cons_t[] = {{0, "MIN"}, {51200, "MAX"}, {0, NULL}};
-consvar_t cv_maxsend = {"maxsend", "1024", CV_SAVE, maxsend_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_maxsend = {"maxsend", "4096", CV_SAVE, maxsend_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_noticedownload = {"noticedownload", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+
+// Speed of file downloading (in packets per tic)
+static CV_PossibleValue_t downloadspeed_cons_t[] = {{0, "MIN"}, {32, "MAX"}, {0, NULL}};
+consvar_t cv_downloadspeed = {"downloadspeed", "16", CV_SAVE, downloadspeed_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
static void Got_AddPlayer(UINT8 **p, INT32 playernum);
@@ -2719,6 +2863,13 @@ void D_ClientServerInit(void)
COM_AddCommand("reloadbans", Command_ReloadBan);
COM_AddCommand("connect", Command_connect);
COM_AddCommand("nodes", Command_Nodes);
+#ifdef PACKETDROP
+ COM_AddCommand("drop", Command_Drop);
+ COM_AddCommand("droprate", Command_Droprate);
+#endif
+#ifdef _DEBUG
+ COM_AddCommand("numnodes", Command_Numnodes);
+#endif
#endif
RegisterNetXCmd(XD_KICK, Got_KickCmd);
@@ -2754,6 +2905,7 @@ static void ResetNode(INT32 node)
supposedtics[node] = gametic;
nodewaiting[node] = 0;
playerpernode[node] = 0;
+ sendingsavegame[node] = false;
}
void SV_ResetServer(void)
@@ -2762,7 +2914,7 @@ void SV_ResetServer(void)
// +1 because this command will be executed in com_executebuffer in
// tryruntic so gametic will be incremented, anyway maketic > gametic
- // is not a issue
+ // is not an issue
maketic = gametic + 1;
neededtic = maketic;
@@ -2816,7 +2968,7 @@ static inline void SV_GenContext(void)
for (i = 0; i < 8; i++)
{
const char a = M_RandomKey(26*2);
- if (a <= 26) // uppercase
+ if (a < 26) // uppercase
server_context[i] = 'A'+a;
else // lowercase
server_context[i] = 'a'+(a-26);
@@ -2851,7 +3003,7 @@ void D_QuitNetGame(void)
if (serverrunning && ms_RoomId > 0)
UnregisterServer();
}
- else if (servernode > 0 && servernode < MAXNETNODES && nodeingame[(UINT8)servernode]!=0)
+ else if (servernode > 0 && servernode < MAXNETNODES && nodeingame[(UINT8)servernode])
{
netbuffer->packettype = PT_CLIENTQUIT;
HSendPacket(servernode, true, 0, 0);
@@ -2872,12 +3024,12 @@ void D_QuitNetGame(void)
#endif
}
-// add a node to the game (player will follow at map change or at savegame....)
+// Adds a node to the game (player will follow at map change or at savegame....)
static inline void SV_AddNode(INT32 node)
{
nettics[node] = gametic;
supposedtics[node] = gametic;
- // little hack because the server connect to itself and put
+ // little hack because the server connects to itself and puts
// nodeingame when connected not here
if (node)
nodeingame[node] = true;
@@ -3027,7 +3179,7 @@ static boolean SV_AddWaitingPlayers(void)
void CL_AddSplitscreenPlayer(void)
{
- if (cl_mode == cl_connected)
+ if (cl_mode == CL_CONNECTED)
CL_SendJoin();
}
@@ -3035,7 +3187,7 @@ void CL_RemoveSplitscreenPlayer(void)
{
XBOXSTATIC UINT8 buf[2];
- if (cl_mode != cl_connected)
+ if (cl_mode != CL_CONNECTED)
return;
buf[0] = (UINT8)secondarydisplayplayer;
@@ -3046,7 +3198,7 @@ void CL_RemoveSplitscreenPlayer(void)
// is there a game running
boolean Playing(void)
{
- return (server && serverrunning) || (!server && cl_mode == cl_connected);
+ return (server && serverrunning) || (client && cl_mode == CL_CONNECTED);
}
boolean SV_SpawnServer(void)
@@ -3094,7 +3246,7 @@ void SV_StopServer(void)
D_Clearticcmd(i);
consoleplayer = 0;
- cl_mode = cl_searching;
+ cl_mode = CL_SEARCHING;
maketic = gametic+1;
neededtic = maketic;
serverrunning = false;
@@ -3140,6 +3292,11 @@ static size_t TotalTextCmdPerTic(tic_t tic)
return total;
}
+/** Called when a PT_CLIENTJOIN packet is received
+ *
+ * \param node The packet sender
+ *
+ */
static void HandleConnect(SINT8 node)
{
if (bannednode && bannednode[node])
@@ -3171,6 +3328,9 @@ static void HandleConnect(SINT8 node)
#endif
SV_AddNode(node);
+ /// \note Wait what???
+ /// What if the gamestate takes more than one second to get downloaded?
+ /// Or if a lagspike happens?
// you get a free second before desynch checks. use it wisely.
SV_InitResynchVars(node);
@@ -3179,6 +3339,7 @@ static void HandleConnect(SINT8 node)
if (!SV_SendServerConfig(node))
{
G_SetGamestate(backupstate);
+ /// \note Shouldn't SV_SendRefuse be called before ResetNode?
ResetNode(node);
SV_SendRefuse(node, M_GetText("Server couldn't send info, please try again"));
/// \todo fix this !!!
@@ -3209,6 +3370,11 @@ static void HandleConnect(SINT8 node)
}
}
+/** Called when a PT_SERVERSHUTDOWN packet is received
+ *
+ * \param node The packet sender (should be the server)
+ *
+ */
static void HandleShutdown(SINT8 node)
{
(void)node;
@@ -3218,6 +3384,11 @@ static void HandleShutdown(SINT8 node)
M_StartMessage(M_GetText("Server has shutdown\n\nPress Esc\n"), NULL, MM_NOTHING);
}
+/** Called when a PT_NODETIMEOUT packet is received
+ *
+ * \param node The packet sender (should be the server)
+ *
+ */
static void HandleTimeout(SINT8 node)
{
(void)node;
@@ -3228,6 +3399,12 @@ static void HandleTimeout(SINT8 node)
}
#ifndef NONET
+/** Called when a PT_SERVERINFO packet is received
+ *
+ * \param node The packet sender
+ * \note What happens if the packet comes from a client or something like that?
+ *
+ */
static void HandleServerInfo(SINT8 node)
{
// compute ping in ms
@@ -3241,39 +3418,646 @@ static void HandleServerInfo(SINT8 node)
}
#endif
-/** \brief GetPackets
+/** Handles a packet received from a node that isn't in game
+ *
+ * \param node The packet sender
+ * \todo Choose a better name, as the packet can also come from the server apparently?
+ * \sa HandlePacketFromPlayer
+ * \sa GetPackets
+ *
+ */
+static void HandlePacketFromAwayNode(SINT8 node)
+{
+ if (node != servernode)
+ DEBFILE(va("Received packet from unknown host %d\n", node));
- \todo break this 300 line function into multiple functions
-*/
-static void GetPackets(void)
+// macro for packets that should only be sent by the server
+// if it is NOT from the server, bail out and close the connection!
+#define SERVERONLY \
+ if (node != servernode) \
+ { \
+ Net_CloseConnection(node); \
+ break; \
+ }
+ switch (netbuffer->packettype)
+ {
+ case PT_ASKINFOVIAMS:
+#if 0
+ if (server && serverrunning)
+ {
+ INT32 clientnode;
+ if (ms_RoomId < 0) // ignore if we're not actually on the MS right now
+ {
+ Net_CloseConnection(node); // and yes, close connection
+ return;
+ }
+ clientnode = I_NetMakeNode(netbuffer->u.msaskinfo.clientaddr);
+ if (clientnode != -1)
+ {
+ SV_SendServerInfo(clientnode, (tic_t)LONG(netbuffer->u.msaskinfo.time));
+ SV_SendPlayerInfo(clientnode); // Send extra info
+ Net_CloseConnection(clientnode);
+ // Don't close connection to MS...
+ }
+ else
+ Net_CloseConnection(node); // ...unless the IP address is not valid
+ }
+ else
+ Net_CloseConnection(node); // you're not supposed to get it, so ignore it
+#else
+ Net_CloseConnection(node);
+#endif
+ break;
+
+ case PT_ASKINFO:
+ if (server && serverrunning)
+ {
+ SV_SendServerInfo(node, (tic_t)LONG(netbuffer->u.askinfo.time));
+ SV_SendPlayerInfo(node); // Send extra info
+ }
+ Net_CloseConnection(node);
+ break;
+
+ case PT_SERVERREFUSE: // Negative response of client join request
+ if (server && serverrunning)
+ { // But wait I thought I'm the server?
+ Net_CloseConnection(node);
+ break;
+ }
+ SERVERONLY
+ if (cl_mode == CL_WAITJOINRESPONSE)
+ {
+ // Save the reason so it can be displayed after quitting the netgame
+ char *reason = strdup(netbuffer->u.serverrefuse.reason);
+ if (!reason)
+ I_Error("Out of memory!\n");
+
+ D_QuitNetGame();
+ CL_Reset();
+ D_StartTitle();
+
+ M_StartMessage(va(M_GetText("Server refuses connection\n\nReason:\n%s"),
+ reason), NULL, MM_NOTHING);
+
+ free(reason);
+
+ // Will be reset by caller. Signals refusal.
+ cl_mode = CL_ABORTED;
+ }
+ break;
+
+ case PT_SERVERCFG: // Positive response of client join request
+ {
+ INT32 j;
+ UINT8 *scp;
+
+ if (server && serverrunning && node != servernode)
+ { // but wait I thought I'm the server?
+ Net_CloseConnection(node);
+ break;
+ }
+ SERVERONLY
+ /// \note how would this happen? and is it doing the right thing if it does?
+ if (cl_mode != CL_WAITJOINRESPONSE)
+ break;
+
+ if (client)
+ {
+ maketic = gametic = neededtic = (tic_t)LONG(netbuffer->u.servercfg.gametic);
+ gametype = netbuffer->u.servercfg.gametype;
+ modifiedgame = netbuffer->u.servercfg.modifiedgame;
+ adminplayer = netbuffer->u.servercfg.adminplayer;
+ memcpy(server_context, netbuffer->u.servercfg.server_context, 8);
+ }
+
+ nodeingame[(UINT8)servernode] = true;
+ serverplayer = netbuffer->u.servercfg.serverplayer;
+ doomcom->numslots = SHORT(netbuffer->u.servercfg.totalslotnum);
+ mynode = netbuffer->u.servercfg.clientnode;
+ if (serverplayer >= 0)
+ playernode[(UINT8)serverplayer] = servernode;
+
+ if (netgame)
+#ifdef JOININGAME
+ CONS_Printf(M_GetText("Join accepted, waiting for complete game state...\n"));
+#else
+ CONS_Printf(M_GetText("Join accepted, waiting for next level change...\n"));
+#endif
+ DEBFILE(va("Server accept join gametic=%u mynode=%d\n", gametic, mynode));
+
+ memset(playeringame, 0, sizeof(playeringame));
+ for (j = 0; j < MAXPLAYERS; j++)
+ {
+ if (netbuffer->u.servercfg.playerskins[j] == 0xFF
+ && netbuffer->u.servercfg.playercolor[j] == 0xFF
+ && netbuffer->u.servercfg.playeravailabilities[j] == 0xFFFFFFFF)
+ continue; // not in game
+
+ playeringame[j] = true;
+ players[j].availabilities = (UINT32)LONG(netbuffer->u.servercfg.playeravailabilities[j]);
+ SetPlayerSkinByNum(j, (INT32)netbuffer->u.servercfg.playerskins[j]);
+ players[j].skincolor = netbuffer->u.servercfg.playercolor[j];
+ }
+
+ scp = netbuffer->u.servercfg.varlengthinputs;
+ CV_LoadPlayerNames(&scp);
+ CV_LoadNetVars(&scp);
+#ifdef JOININGAME
+ /// \note Wait. What if a Lua script uses some global custom variables synched with the NetVars hook?
+ /// Shouldn't them be downloaded even at intermission time?
+ /// Also, according to HandleConnect, the server will send the savegame even during intermission...
+ if (netbuffer->u.servercfg.gamestate == GS_LEVEL/* ||
+ netbuffer->u.servercfg.gamestate == GS_INTERMISSION*/)
+ cl_mode = CL_DOWNLOADSAVEGAME;
+ else
+#endif
+ cl_mode = CL_CONNECTED;
+ break;
+ }
+
+ // Handled in d_netfil.c
+ case PT_FILEFRAGMENT:
+ if (server)
+ { // But wait I thought I'm the server?
+ Net_CloseConnection(node);
+ break;
+ }
+ SERVERONLY
+ Got_Filetxpak();
+ break;
+
+ case PT_REQUESTFILE:
+ if (server)
+ {
+ if (!cv_downloading.value || !Got_RequestFilePak(node))
+ Net_CloseConnection(node); // close connection if one of the requested files could not be sent, or you disabled downloading anyway
+ }
+ else
+ Net_CloseConnection(node); // nope
+ break;
+
+ case PT_NODETIMEOUT:
+ case PT_CLIENTQUIT:
+ if (server)
+ Net_CloseConnection(node);
+ break;
+
+ case PT_CLIENTCMD:
+ break; // This is not an "unknown packet"
+
+ case PT_SERVERTICS:
+ // Do not remove my own server (we have just get a out of order packet)
+ if (node == servernode)
+ break;
+
+ default:
+ DEBFILE(va("unknown packet received (%d) from unknown host\n",netbuffer->packettype));
+ Net_CloseConnection(node);
+ break; // Ignore it
+
+ }
+#undef SERVERONLY
+}
+
+/** Handles a packet received from a node that is in game
+ *
+ * \param node The packet sender
+ * \todo Choose a better name
+ * \sa HandlePacketFromAwayNode
+ * \sa GetPackets
+ *
+ */
+static void HandlePacketFromPlayer(SINT8 node)
{FILESTAMP
XBOXSTATIC INT32 netconsole;
- XBOXSTATIC SINT8 node;
- XBOXSTATIC tic_t realend,realstart;
+ XBOXSTATIC tic_t realend, realstart;
XBOXSTATIC UINT8 *pak, *txtpak, numtxtpak;
FILESTAMP
+ txtpak = NULL;
+
+ if (dedicated && node == 0)
+ netconsole = 0;
+ else
+ netconsole = nodetoplayer[node];
+#ifdef PARANOIA
+ if (netconsole >= MAXPLAYERS)
+ I_Error("bad table nodetoplayer: node %d player %d", doomcom->remotenode, netconsole);
+#endif
+
+ switch (netbuffer->packettype)
+ {
+// -------------------------------------------- SERVER RECEIVE ----------
+ case PT_RESYNCHGET:
+ if (client)
+ break;
+ SV_AcknowledgeResynchAck(netconsole, netbuffer->u.resynchgot);
+ break;
+ case PT_CLIENTCMD:
+ case PT_CLIENT2CMD:
+ case PT_CLIENTMIS:
+ case PT_CLIENT2MIS:
+ case PT_NODEKEEPALIVE:
+ case PT_NODEKEEPALIVEMIS:
+ if (client)
+ break;
+
+ // Ignore tics from those not synched
+ if (resynch_inprogress[node])
+ break;
+
+ // To save bytes, only the low byte of tic numbers are sent
+ // Use ExpandTics to figure out what the rest of the bytes are
+ realstart = ExpandTics(netbuffer->u.clientpak.client_tic);
+ realend = ExpandTics(netbuffer->u.clientpak.resendfrom);
+
+ if (netbuffer->packettype == PT_CLIENTMIS || netbuffer->packettype == PT_CLIENT2MIS
+ || netbuffer->packettype == PT_NODEKEEPALIVEMIS
+ || supposedtics[node] < realend)
+ {
+ supposedtics[node] = realend;
+ }
+ // Discard out of order packet
+ if (nettics[node] > realend)
+ {
+ DEBFILE(va("out of order ticcmd discarded nettics = %u\n", nettics[node]));
+ break;
+ }
+
+ // Update the nettics
+ nettics[node] = realend;
+
+ // Don't do anything for packets of type NODEKEEPALIVE?
+ if (netconsole == -1 || netbuffer->packettype == PT_NODEKEEPALIVE
+ || netbuffer->packettype == PT_NODEKEEPALIVEMIS)
+ break;
+
+ // If a client sends a ticcmd it should mean they are done receiving the savegame
+ sendingsavegame[node] = false;
+
+ // As long as clients send valid ticcmds, the server can keep running, so reset the timeout
+ /// \todo Use a separate cvar for that kind of timeout?
+ freezetimeout[node] = I_GetTime() + connectiontimeout;
+
+ // Copy ticcmd
+ G_MoveTiccmd(&netcmds[maketic%BACKUPTICS][netconsole], &netbuffer->u.clientpak.cmd, 1);
+
+ // Check ticcmd for "speed hacks"
+ if (netcmds[maketic%BACKUPTICS][netconsole].forwardmove > MAXPLMOVE || netcmds[maketic%BACKUPTICS][netconsole].forwardmove < -MAXPLMOVE
+ || netcmds[maketic%BACKUPTICS][netconsole].sidemove > MAXPLMOVE || netcmds[maketic%BACKUPTICS][netconsole].sidemove < -MAXPLMOVE)
+ {
+ XBOXSTATIC char buf[2];
+ CONS_Alert(CONS_WARNING, M_GetText("Illegal movement value received from node %d\n"), netconsole);
+ //D_Clearticcmd(k);
+
+ buf[0] = (char)netconsole;
+ buf[1] = KICK_MSG_CON_FAIL;
+ SendNetXCmd(XD_KICK, &buf, 2);
+ break;
+ }
+
+ // Splitscreen cmd
+ if ((netbuffer->packettype == PT_CLIENT2CMD || netbuffer->packettype == PT_CLIENT2MIS)
+ && nodetoplayer2[node] >= 0)
+ G_MoveTiccmd(&netcmds[maketic%BACKUPTICS][(UINT8)nodetoplayer2[node]],
+ &netbuffer->u.client2pak.cmd2, 1);
+
+ // A delay before we check resynching
+ // Used on join or just after a synch fail
+ if (resynch_delay[node])
+ {
+ --resynch_delay[node];
+ break;
+ }
+ // Check player consistancy during the level
+ if (realstart <= gametic && realstart > gametic - BACKUPTICS+1 && gamestate == GS_LEVEL
+ && consistancy[realstart%BACKUPTICS] != SHORT(netbuffer->u.clientpak.consistancy))
+ {
+ SV_RequireResynch(node);
+
+ if (cv_resynchattempts.value && resynch_score[node] <= (unsigned)cv_resynchattempts.value*250)
+ {
+ if (cv_blamecfail.value)
+ CONS_Printf(M_GetText("Synch failure for player %d (%s); expected %hd, got %hd\n"),
+ netconsole+1, player_names[netconsole],
+ consistancy[realstart%BACKUPTICS],
+ SHORT(netbuffer->u.clientpak.consistancy));
+ DEBFILE(va("Restoring player %d (synch failure) [%update] %d!=%d\n",
+ netconsole, realstart, consistancy[realstart%BACKUPTICS],
+ SHORT(netbuffer->u.clientpak.consistancy)));
+ break;
+ }
+ else
+ {
+ XBOXSTATIC UINT8 buf[3];
+
+ buf[0] = (UINT8)netconsole;
+ buf[1] = KICK_MSG_CON_FAIL;
+ SendNetXCmd(XD_KICK, &buf, 2);
+ DEBFILE(va("player %d kicked (synch failure) [%u] %d!=%d\n",
+ netconsole, realstart, consistancy[realstart%BACKUPTICS],
+ SHORT(netbuffer->u.clientpak.consistancy)));
+ break;
+ }
+ }
+ else if (resynch_score[node])
+ --resynch_score[node];
+ break;
+ case PT_TEXTCMD2: // splitscreen special
+ netconsole = nodetoplayer2[node];
+ case PT_TEXTCMD:
+ if (client)
+ break;
+
+ if (netconsole < 0 || netconsole >= MAXPLAYERS)
+ Net_UnAcknowledgePacket(node);
+ else
+ {
+ size_t j;
+ tic_t tic = maketic;
+ UINT8 *textcmd;
+
+ // ignore if the textcmd has a reported size of zero
+ // this shouldn't be sent at all
+ if (!netbuffer->u.textcmd[0])
+ {
+ DEBFILE(va("GetPacket: Textcmd with size 0 detected! (node %u, player %d)\n",
+ node, netconsole));
+ Net_UnAcknowledgePacket(node);
+ break;
+ }
+
+ // ignore if the textcmd size var is actually larger than it should be
+ // BASEPACKETSIZE + 1 (for size) + textcmd[0] should == datalength
+ if (netbuffer->u.textcmd[0] > (size_t)doomcom->datalength-BASEPACKETSIZE-1)
+ {
+ DEBFILE(va("GetPacket: Bad Textcmd packet size! (expected %d, actual %s, node %u, player %d)\n",
+ netbuffer->u.textcmd[0], sizeu1((size_t)doomcom->datalength-BASEPACKETSIZE-1),
+ node, netconsole));
+ Net_UnAcknowledgePacket(node);
+ break;
+ }
+
+ // check if tic that we are making isn't too large else we cannot send it :(
+ // doomcom->numslots+1 "+1" since doomcom->numslots can change within this time and sent time
+ j = software_MAXPACKETLENGTH
+ - (netbuffer->u.textcmd[0]+2+BASESERVERTICSSIZE
+ + (doomcom->numslots+1)*sizeof(ticcmd_t));
+
+ // search a tic that have enougth space in the ticcmd
+ while ((textcmd = D_GetExistingTextcmd(tic, netconsole)),
+ (TotalTextCmdPerTic(tic) > j || netbuffer->u.textcmd[0] + (textcmd ? textcmd[0] : 0) > MAXTEXTCMD)
+ && tic < firstticstosend + BACKUPTICS)
+ tic++;
+
+ if (tic >= firstticstosend + BACKUPTICS)
+ {
+ DEBFILE(va("GetPacket: Textcmd too long (max %s, used %s, mak %d, "
+ "tosend %u, node %u, player %d)\n", sizeu1(j), sizeu2(TotalTextCmdPerTic(maketic)),
+ maketic, firstticstosend, node, netconsole));
+ Net_UnAcknowledgePacket(node);
+ break;
+ }
+
+ // Make sure we have a buffer
+ if (!textcmd) textcmd = D_GetTextcmd(tic, netconsole);
+
+ DEBFILE(va("textcmd put in tic %u at position %d (player %d) ftts %u mk %u\n",
+ tic, textcmd[0]+1, netconsole, firstticstosend, maketic));
+
+ M_Memcpy(&textcmd[textcmd[0]+1], netbuffer->u.textcmd+1, netbuffer->u.textcmd[0]);
+ textcmd[0] += (UINT8)netbuffer->u.textcmd[0];
+ }
+ break;
+ case PT_NODETIMEOUT:
+ case PT_CLIENTQUIT:
+ if (client)
+ break;
+
+ // nodeingame will be put false in the execution of kick command
+ // this allow to send some packets to the quitting client to have their ack back
+ nodewaiting[node] = 0;
+ if (netconsole != -1 && playeringame[netconsole])
+ {
+ XBOXSTATIC UINT8 buf[2];
+ buf[0] = (UINT8)netconsole;
+ if (netbuffer->packettype == PT_NODETIMEOUT)
+ buf[1] = KICK_MSG_TIMEOUT;
+ else
+ buf[1] = KICK_MSG_PLAYER_QUIT;
+ SendNetXCmd(XD_KICK, &buf, 2);
+ nodetoplayer[node] = -1;
+ if (nodetoplayer2[node] != -1 && nodetoplayer2[node] >= 0
+ && playeringame[(UINT8)nodetoplayer2[node]])
+ {
+ buf[0] = nodetoplayer2[node];
+ SendNetXCmd(XD_KICK, &buf, 2);
+ nodetoplayer2[node] = -1;
+ }
+ }
+ Net_CloseConnection(node);
+ nodeingame[node] = false;
+ break;
+// -------------------------------------------- CLIENT RECEIVE ----------
+ case PT_RESYNCHEND:
+ // Only accept PT_RESYNCHEND from the server.
+ if (node != servernode)
+ {
+ CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_RESYNCHEND", node);
+
+ if (server)
+ {
+ XBOXSTATIC UINT8 buf[2];
+ buf[0] = (UINT8)node;
+ buf[1] = KICK_MSG_CON_FAIL;
+ SendNetXCmd(XD_KICK, &buf, 2);
+ }
+
+ break;
+ }
+ resynch_local_inprogress = false;
+
+ P_SetRandSeed(netbuffer->u.resynchend.randomseed);
+
+ if (gametype == GT_CTF)
+ resynch_read_ctf(&netbuffer->u.resynchend);
+ resynch_read_others(&netbuffer->u.resynchend);
+
+ break;
+ case PT_SERVERTICS:
+ // Only accept PT_SERVERTICS from the server.
+ if (node != servernode)
+ {
+ CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_SERVERTICS", node);
+
+ if (server)
+ {
+ XBOXSTATIC UINT8 buf[2];
+ buf[0] = (UINT8)node;
+ buf[1] = KICK_MSG_CON_FAIL;
+ SendNetXCmd(XD_KICK, &buf, 2);
+ }
+
+ break;
+ }
+
+ realstart = ExpandTics(netbuffer->u.serverpak.starttic);
+ realend = realstart + netbuffer->u.serverpak.numtics;
+
+ if (!txtpak)
+ txtpak = (UINT8 *)&netbuffer->u.serverpak.cmds[netbuffer->u.serverpak.numslots
+ * netbuffer->u.serverpak.numtics];
+
+ if (realend > gametic + BACKUPTICS)
+ realend = gametic + BACKUPTICS;
+ cl_packetmissed = realstart > neededtic;
+
+ if (realstart <= neededtic && realend > neededtic)
+ {
+ tic_t i, j;
+ pak = (UINT8 *)&netbuffer->u.serverpak.cmds;
+
+ for (i = realstart; i < realend; i++)
+ {
+ // clear first
+ D_Clearticcmd(i);
+
+ // copy the tics
+ pak = G_ScpyTiccmd(netcmds[i%BACKUPTICS], pak,
+ netbuffer->u.serverpak.numslots*sizeof (ticcmd_t));
+
+ // copy the textcmds
+ numtxtpak = *txtpak++;
+ for (j = 0; j < numtxtpak; j++)
+ {
+ INT32 k = *txtpak++; // playernum
+ const size_t txtsize = txtpak[0]+1;
+
+ M_Memcpy(D_GetTextcmd(i, k), txtpak, txtsize);
+ txtpak += txtsize;
+ }
+ }
+
+ neededtic = realend;
+ }
+ else
+ {
+ DEBFILE(va("frame not in bound: %u\n", neededtic));
+ /*if (realend < neededtic - 2 * TICRATE || neededtic + 2 * TICRATE < realstart)
+ I_Error("Received an out of order PT_SERVERTICS packet!\n"
+ "Got tics %d-%d, needed tic %d\n\n"
+ "Please report this crash on the Master Board,\n"
+ "IRC or Discord so it can be fixed.\n", (INT32)realstart, (INT32)realend, (INT32)neededtic);*/
+ }
+ break;
+ case PT_RESYNCHING:
+ // Only accept PT_RESYNCHING from the server.
+ if (node != servernode)
+ {
+ CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_RESYNCHING", node);
+
+ if (server)
+ {
+ XBOXSTATIC char buf[2];
+ buf[0] = (char)node;
+ buf[1] = KICK_MSG_CON_FAIL;
+ SendNetXCmd(XD_KICK, &buf, 2);
+ }
+
+ break;
+ }
+ resynch_local_inprogress = true;
+ CL_AcknowledgeResynch(&netbuffer->u.resynchpak);
+ break;
+#ifdef NEWPING
+ case PT_PING:
+ // Only accept PT_PING from the server.
+ if (node != servernode)
+ {
+ CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_PING", node);
+
+ if (server)
+ {
+ XBOXSTATIC char buf[2];
+ buf[0] = (char)node;
+ buf[1] = KICK_MSG_CON_FAIL;
+ SendNetXCmd(XD_KICK, &buf, 2);
+ }
+
+ break;
+ }
+
+ //Update client ping table from the server.
+ if (client)
+ {
+ INT32 i;
+ for (i = 0; i < MAXPLAYERS; i++)
+ if (playeringame[i])
+ playerpingtable[i] = (tic_t)netbuffer->u.pingtable[i];
+ }
+
+ break;
+#endif
+ case PT_SERVERCFG:
+ break;
+ case PT_FILEFRAGMENT:
+ // Only accept PT_FILEFRAGMENT from the server.
+ if (node != servernode)
+ {
+ CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_FILEFRAGMENT", node);
+
+ if (server)
+ {
+ XBOXSTATIC UINT8 buf[2];
+ buf[0] = (UINT8)node;
+ buf[1] = KICK_MSG_CON_FAIL;
+ SendNetXCmd(XD_KICK, &buf, 2);
+ }
+
+ break;
+ }
+ if (client)
+ Got_Filetxpak();
+ break;
+ default:
+ DEBFILE(va("UNKNOWN PACKET TYPE RECEIVED %d from host %d\n",
+ netbuffer->packettype, node));
+ } // end switch
+}
+
+/** Handles all received packets, if any
+ *
+ * \todo Add details to this description (lol)
+ *
+ */
+static void GetPackets(void)
+{FILESTAMP
+ XBOXSTATIC SINT8 node; // The packet sender
+FILESTAMP
+
player_joining = false;
while (HGetPacket())
{
node = (SINT8)doomcom->remotenode;
+
if (netbuffer->packettype == PT_CLIENTJOIN && server)
{
HandleConnect(node);
continue;
}
- if (netbuffer->packettype == PT_SERVERSHUTDOWN && node == servernode
- && !server && cl_mode != cl_searching)
+ if (node == servernode && client && cl_mode != CL_SEARCHING)
{
- HandleShutdown(node);
- continue;
- }
- if (netbuffer->packettype == PT_NODETIMEOUT && node == servernode
- && !server && cl_mode != cl_searching)
- {
- HandleTimeout(node);
- continue;
+ if (netbuffer->packettype == PT_SERVERSHUTDOWN)
+ {
+ HandleShutdown(node);
+ continue;
+ }
+ if (netbuffer->packettype == PT_NODETIMEOUT)
+ {
+ HandleTimeout(node);
+ continue;
+ }
}
#ifndef NONET
@@ -3287,481 +4071,13 @@ FILESTAMP
if (netbuffer->packettype == PT_PLAYERINFO)
continue; // We do nothing with PLAYERINFO, that's for the MS browser.
- if (!nodeingame[node])
- {
- if (node != servernode)
- DEBFILE(va("Received packet from unknown host %d\n", node));
-
- // anyone trying to join
- switch (netbuffer->packettype)
- {
- case PT_ASKINFOVIAMS:
- if (server && serverrunning)
- {
- INT32 clientnode = I_NetMakeNode(netbuffer->u.msaskinfo.clientaddr);
- SV_SendServerInfo(clientnode, (tic_t)LONG(netbuffer->u.msaskinfo.time));
- SV_SendPlayerInfo(clientnode); // send extra info
- Net_CloseConnection(clientnode);
- // Don't close connection to MS.
- }
- break;
-
- case PT_ASKINFO:
- if (server && serverrunning)
- {
- SV_SendServerInfo(node, (tic_t)LONG(netbuffer->u.askinfo.time));
- SV_SendPlayerInfo(node); // send extra info
- Net_CloseConnection(node);
- }
- break;
- case PT_SERVERREFUSE: // negative response of client join request
- if (server && serverrunning)
- { // but wait I thought I'm the server?
- Net_CloseConnection(node);
- break;
- }
- if (cl_mode == cl_waitjoinresponse)
- {
- D_QuitNetGame();
- CL_Reset();
- D_StartTitle();
-
- M_StartMessage(va(M_GetText("Server refuses connection\n\nReason:\n%s"),
- netbuffer->u.serverrefuse.reason), NULL, MM_NOTHING);
-
- // Will be reset by caller. Signals refusal.
- cl_mode = cl_aborted;
- }
- break;
- case PT_SERVERCFG: // positive response of client join request
- {
- INT32 j;
- UINT8 *scp;
-
- if (server && serverrunning && node != servernode)
- { // but wait I thought I'm the server?
- Net_CloseConnection(node);
- break;
- }
- /// \note how would this happen? and is it doing the right thing if it does?
- if (cl_mode != cl_waitjoinresponse)
- break;
-
- if (!server)
- {
- maketic = gametic = neededtic = (tic_t)LONG(netbuffer->u.servercfg.gametic);
- gametype = netbuffer->u.servercfg.gametype;
- modifiedgame = netbuffer->u.servercfg.modifiedgame;
- adminplayer = netbuffer->u.servercfg.adminplayer;
- memcpy(server_context, netbuffer->u.servercfg.server_context, 8);
- }
-
- nodeingame[(UINT8)servernode] = true;
- serverplayer = netbuffer->u.servercfg.serverplayer;
- doomcom->numslots = SHORT(netbuffer->u.servercfg.totalslotnum);
- mynode = netbuffer->u.servercfg.clientnode;
- if (serverplayer >= 0)
- playernode[(UINT8)serverplayer] = servernode;
-
- if (netgame)
-#ifdef JOININGAME
- CONS_Printf(M_GetText("Join accepted, waiting for complete game state...\n"));
-#else
- CONS_Printf(M_GetText("Join accepted, waiting for next level change...\n"));
-#endif
- DEBFILE(va("Server accept join gametic=%u mynode=%d\n", gametic, mynode));
-
- memset(playeringame, 0, sizeof(playeringame));
- for (j = 0; j < MAXPLAYERS; j++)
- {
- if (netbuffer->u.servercfg.playerskins[j] == 0xFF
- && netbuffer->u.servercfg.playercolor[j] == 0xFF)
- continue; // not in game
-
- playeringame[j] = true;
- SetPlayerSkinByNum(j, (INT32)netbuffer->u.servercfg.playerskins[j]);
- players[j].skincolor = netbuffer->u.servercfg.playercolor[j];
- }
-
- scp = netbuffer->u.servercfg.varlengthinputs;
- CV_LoadPlayerNames(&scp);
- CV_LoadNetVars(&scp);
-#ifdef JOININGAME
- if (netbuffer->u.servercfg.gamestate == GS_LEVEL/* ||
- netbuffer->u.servercfg.gamestate == GS_INTERMISSION*/)
- cl_mode = cl_downloadsavegame;
- else
-#endif
- cl_mode = cl_connected;
- break;
- }
- // handled in d_netfil.c
- case PT_FILEFRAGMENT:
- if (server)
- { // but wait I thought I'm the server?
- Net_CloseConnection(node);
- break;
- }
- else
- Got_Filetxpak();
- break;
- case PT_REQUESTFILE:
- if (server)
- Got_RequestFilePak(node);
- break;
- case PT_NODETIMEOUT:
- case PT_CLIENTQUIT:
- if (server)
- Net_CloseConnection(node);
- break;
- case PT_CLIENTCMD:
- break; // this is not an "unknown packet"
- case PT_SERVERTICS:
- // do not remove my own server (we have just get a out of order packet)
- if (node == servernode)
- break;
- default:
- DEBFILE(va("unknown packet received (%d) from unknown host\n",netbuffer->packettype));
- Net_CloseConnection(node);
- break; // ignore it
- } // switch
- continue; //while
- }
- if (dedicated && node == 0) netconsole = 0;
- else netconsole = nodetoplayer[node];
-#ifdef PARANOIA
- if (netconsole >= MAXPLAYERS)
- I_Error("bad table nodetoplayer: node %d player %d", doomcom->remotenode, netconsole);
-#endif
-
- txtpak = NULL;
-
- switch (netbuffer->packettype)
- {
-// -------------------------------------------- SERVER RECEIVE ----------
- case PT_RESYNCHGET:
- SV_AcknowledgeResynchAck(netconsole, netbuffer->u.resynchgot);
- break;
- case PT_CLIENTCMD:
- case PT_CLIENT2CMD:
- case PT_CLIENTMIS:
- case PT_CLIENT2MIS:
- case PT_NODEKEEPALIVE:
- case PT_NODEKEEPALIVEMIS:
- if (!server)
- break;
-
- // ignore tics from those not synched
- if (resynch_inprogress[node])
- break;
-
- // to save bytes, only the low byte of tic numbers are sent
- // Figure out what the rest of the bytes are
- realstart = ExpandTics(netbuffer->u.clientpak.client_tic);
- realend = ExpandTics(netbuffer->u.clientpak.resendfrom);
-
- if (netbuffer->packettype == PT_CLIENTMIS || netbuffer->packettype == PT_CLIENT2MIS
- || netbuffer->packettype == PT_NODEKEEPALIVEMIS
- || supposedtics[node] < realend)
- {
- supposedtics[node] = realend;
- }
- // discard out of order packet
- if (nettics[node] > realend)
- {
- DEBFILE(va("out of order ticcmd discarded nettics = %u\n", nettics[node]));
- break;
- }
-
- // update the nettics
- nettics[node] = realend;
-
- // don't do anything for packets of type NODEKEEPALIVE?
- if (netconsole == -1 || netbuffer->packettype == PT_NODEKEEPALIVE
- || netbuffer->packettype == PT_NODEKEEPALIVEMIS)
- break;
-
- // copy ticcmd
- G_MoveTiccmd(&netcmds[maketic%BACKUPTICS][netconsole], &netbuffer->u.clientpak.cmd, 1);
-
- // check ticcmd for "speed hacks"
- if (netcmds[maketic%BACKUPTICS][netconsole].forwardmove > MAXPLMOVE || netcmds[maketic%BACKUPTICS][netconsole].forwardmove < -MAXPLMOVE
- || netcmds[maketic%BACKUPTICS][netconsole].sidemove > MAXPLMOVE || netcmds[maketic%BACKUPTICS][netconsole].sidemove < -MAXPLMOVE)
- {
- XBOXSTATIC char buf[2];
- CONS_Alert(CONS_WARNING, M_GetText("Illegal movement value recieved from node %d\n"), netconsole);
- //D_Clearticcmd(k);
-
- buf[0] = (char)netconsole;
- buf[1] = KICK_MSG_CON_FAIL;
- SendNetXCmd(XD_KICK, &buf, 2);
- break;
- }
-
- // splitscreen cmd
- if (netbuffer->packettype == PT_CLIENT2CMD && nodetoplayer2[node] >= 0)
- G_MoveTiccmd(&netcmds[maketic%BACKUPTICS][(UINT8)nodetoplayer2[node]],
- &netbuffer->u.client2pak.cmd2, 1);
-
- // a delay before we check resynching
- // used on join or just after a synch fail
- if (resynch_delay[node])
- {
- --resynch_delay[node];
- break;
- }
- // check player consistancy during the level
- if (realstart <= gametic && realstart > gametic - BACKUPTICS+1 && gamestate == GS_LEVEL
- && consistancy[realstart%BACKUPTICS] != SHORT(netbuffer->u.clientpak.consistancy))
- {
- SV_RequireResynch(node);
-
- if (cv_resynchattempts.value && resynch_score[node] <= (unsigned)cv_resynchattempts.value*250)
- {
- if (cv_blamecfail.value)
- CONS_Printf(M_GetText("Synch failure for player %d (%s); expected %hd, got %hd\n"),
- netconsole+1, player_names[netconsole],
- consistancy[realstart%BACKUPTICS],
- SHORT(netbuffer->u.clientpak.consistancy));
- DEBFILE(va("Restoring player %d (synch failure) [%update] %d!=%d\n",
- netconsole, realstart, consistancy[realstart%BACKUPTICS],
- SHORT(netbuffer->u.clientpak.consistancy)));
- break;
- }
- else
- {
- XBOXSTATIC UINT8 buf[3];
-
- buf[0] = (UINT8)netconsole;
- buf[1] = KICK_MSG_CON_FAIL;
- SendNetXCmd(XD_KICK, &buf, 2);
- DEBFILE(va("player %d kicked (synch failure) [%u] %d!=%d\n",
- netconsole, realstart, consistancy[realstart%BACKUPTICS],
- SHORT(netbuffer->u.clientpak.consistancy)));
- break;
- }
- }
- else if (resynch_score[node])
- --resynch_score[node];
- break;
- case PT_TEXTCMD2: // splitscreen special
- netconsole = nodetoplayer2[node];
- case PT_TEXTCMD:
- if (!server)
- break;
-
- if (netconsole < 0 || netconsole >= MAXPLAYERS)
- Net_UnAcknowledgPacket(node);
- else
- {
- size_t j;
- tic_t tic = maketic;
- UINT8 *textcmd;
-
- // check if tic that we are making isn't too large else we cannot send it :(
- // doomcom->numslots+1 "+1" since doomcom->numslots can change within this time and sent time
- j = software_MAXPACKETLENGTH
- - (netbuffer->u.textcmd[0]+2+BASESERVERTICSSIZE
- + (doomcom->numslots+1)*sizeof(ticcmd_t));
-
- // search a tic that have enougth space in the ticcmd
- while ((textcmd = D_GetExistingTextcmd(tic, netconsole)),
- (TotalTextCmdPerTic(tic) > j || netbuffer->u.textcmd[0] + (textcmd ? textcmd[0] : 0) > MAXTEXTCMD)
- && tic < firstticstosend + BACKUPTICS)
- tic++;
-
- if (tic >= firstticstosend + BACKUPTICS)
- {
- DEBFILE(va("GetPacket: Textcmd too long (max %s, used %s, mak %d, "
- "tosend %u, node %u, player %d)\n", sizeu1(j), sizeu2(TotalTextCmdPerTic(maketic)),
- maketic, firstticstosend, node, netconsole));
- Net_UnAcknowledgPacket(node);
- break;
- }
-
- // Make sure we have a buffer
- if (!textcmd) textcmd = D_GetTextcmd(tic, netconsole);
-
- DEBFILE(va("textcmd put in tic %u at position %d (player %d) ftts %u mk %u\n",
- tic, textcmd[0]+1, netconsole, firstticstosend, maketic));
-
- M_Memcpy(&textcmd[textcmd[0]+1], netbuffer->u.textcmd+1, netbuffer->u.textcmd[0]);
- textcmd[0] += (UINT8)netbuffer->u.textcmd[0];
- }
- break;
- case PT_NODETIMEOUT:
- case PT_CLIENTQUIT:
- if (!server)
- break;
-
- // nodeingame will be put false in the execution of kick command
- // this allow to send some packets to the quitting client to have their ack back
- nodewaiting[node] = 0;
- if (netconsole != -1 && playeringame[netconsole])
- {
- XBOXSTATIC UINT8 buf[2];
- buf[0] = (UINT8)netconsole;
- if (netbuffer->packettype == PT_NODETIMEOUT)
- buf[1] = KICK_MSG_TIMEOUT;
- else
- buf[1] = KICK_MSG_PLAYER_QUIT;
- SendNetXCmd(XD_KICK, &buf, 2);
- nodetoplayer[node] = -1;
- if (nodetoplayer2[node] != -1 && nodetoplayer2[node] >= 0
- && playeringame[(UINT8)nodetoplayer2[node]])
- {
- buf[0] = nodetoplayer2[node];
- SendNetXCmd(XD_KICK, &buf, 2);
- nodetoplayer2[node] = -1;
- }
- }
- Net_CloseConnection(node);
- nodeingame[node] = false;
- break;
-// -------------------------------------------- CLIENT RECEIVE ----------
- case PT_RESYNCHEND:
- // Only accept PT_RESYNCHEND from the server.
- if (node != servernode)
- {
- CONS_Alert(CONS_WARNING, M_GetText("%s recieved from non-host %d\n"), "PT_RESYNCHEND", node);
-
- if (server)
- {
- XBOXSTATIC UINT8 buf[2];
- buf[0] = (UINT8)node;
- buf[1] = KICK_MSG_CON_FAIL;
- SendNetXCmd(XD_KICK, &buf, 2);
- }
-
- break;
- }
- resynch_local_inprogress = false;
-
- P_SetRandSeed(netbuffer->u.resynchend.randomseed);
-
- if (gametype == GT_CTF)
- resynch_read_ctf(&netbuffer->u.resynchend);
- resynch_read_others(&netbuffer->u.resynchend);
-
- break;
- case PT_SERVERTICS:
- // Only accept PT_SERVERTICS from the server.
- if (node != servernode)
- {
- CONS_Alert(CONS_WARNING, M_GetText("%s recieved from non-host %d\n"), "PT_SERVERTICS", node);
-
- if (server)
- {
- XBOXSTATIC UINT8 buf[2];
- buf[0] = (UINT8)node;
- buf[1] = KICK_MSG_CON_FAIL;
- SendNetXCmd(XD_KICK, &buf, 2);
- }
-
- break;
- }
-
- realstart = ExpandTics(netbuffer->u.serverpak.starttic);
- realend = realstart + netbuffer->u.serverpak.numtics;
-
- if (!txtpak)
- txtpak = (UINT8 *)&netbuffer->u.serverpak.cmds[netbuffer->u.serverpak.numslots
- * netbuffer->u.serverpak.numtics];
-
- if (realend > gametic + BACKUPTICS)
- realend = gametic + BACKUPTICS;
- cl_packetmissed = realstart > neededtic;
-
- if (realstart <= neededtic && realend > neededtic)
- {
- tic_t i, j;
- pak = (UINT8 *)&netbuffer->u.serverpak.cmds;
-
- for (i = realstart; i < realend; i++)
- {
- // clear first
- D_Clearticcmd(i);
-
- // copy the tics
- pak = G_ScpyTiccmd(netcmds[i%BACKUPTICS], pak,
- netbuffer->u.serverpak.numslots*sizeof (ticcmd_t));
-
- // copy the textcmds
- numtxtpak = *txtpak++;
- for (j = 0; j < numtxtpak; j++)
- {
- INT32 k = *txtpak++; // playernum
- const size_t txtsize = txtpak[0]+1;
-
- M_Memcpy(D_GetTextcmd(i, k), txtpak, txtsize);
- txtpak += txtsize;
- }
- }
-
- neededtic = realend;
- }
- else
- DEBFILE(va("frame not in bound: %u\n", neededtic));
- break;
- case PT_RESYNCHING:
- // Only accept PT_RESYNCHING from the server.
- if (node != servernode)
- {
- CONS_Alert(CONS_WARNING, M_GetText("%s recieved from non-host %d\n"), "PT_RESYNCHING", node);
-
- if (server)
- {
- XBOXSTATIC char buf[2];
- buf[0] = (char)node;
- buf[1] = KICK_MSG_CON_FAIL;
- SendNetXCmd(XD_KICK, &buf, 2);
- }
-
- break;
- }
- resynch_local_inprogress = true;
- CL_AcknowledgeResynch(&netbuffer->u.resynchpak);
- break;
-#ifdef NEWPING
- case PT_PING:
- // Only accept PT_PING from the server.
- if (node != servernode)
- {
- CONS_Alert(CONS_WARNING, M_GetText("%s recieved from non-host %d\n"), "PT_PING", node);
-
- if (server)
- {
- XBOXSTATIC char buf[2];
- buf[0] = (char)node;
- buf[1] = KICK_MSG_CON_FAIL;
- SendNetXCmd(XD_KICK, &buf, 2);
- }
-
- break;
- }
-
- //Update client ping table from the server.
- if (!server)
- {
- INT32 i;
- for (i = 0; i < MAXNETNODES; i++)
- if (playeringame[i])
- playerpingtable[i] = (tic_t)netbuffer->u.pingtable[i];
- }
-
- break;
-#endif
- case PT_SERVERCFG:
- break;
- case PT_FILEFRAGMENT:
- if (!server)
- Got_Filetxpak();
- break;
- default:
- DEBFILE(va("UNKNOWN PACKET TYPE RECEIVED %d from host %d\n",
- netbuffer->packettype, node));
- } // end switch
- } // end while
+ // Packet received from someone already playing
+ if (nodeingame[node])
+ HandlePacketFromPlayer(node);
+ // Packet received from someone not playing
+ else
+ HandlePacketFromAwayNode(node);
+ }
}
//
@@ -3776,6 +4092,10 @@ static INT16 Consistancy(void)
{
INT32 i;
UINT32 ret = 0;
+#ifdef MOBJCONSISTANCY
+ thinker_t *th;
+ mobj_t *mo;
+#endif
DEBFILE(va("TIC %u ", gametic));
@@ -3797,6 +4117,77 @@ static INT16 Consistancy(void)
if (!G_PlatformGametype())
ret += P_GetRandSeed();
+#ifdef MOBJCONSISTANCY
+ if (!thinkercap.next)
+ return ret;
+ for (th = thinkercap.next; th != &thinkercap; th = th->next)
+ {
+ if (th->function.acp1 != (actionf_p1)P_MobjThinker)
+ continue;
+
+ mo = (mobj_t *)th;
+
+ if (mo->flags & (MF_SPECIAL | MF_SOLID | MF_PUSHABLE | MF_BOSS | MF_MISSILE | MF_SPRING | MF_MONITOR | MF_FIRE | MF_ENEMY | MF_PAIN | MF_STICKY))
+ {
+ ret -= mo->type;
+ ret += mo->x;
+ ret -= mo->y;
+ ret += mo->z;
+ ret -= mo->momx;
+ ret += mo->momy;
+ ret -= mo->momz;
+ ret += mo->angle;
+ ret -= mo->flags;
+ ret += mo->flags2;
+ ret -= mo->eflags;
+ if (mo->target)
+ {
+ ret += mo->target->type;
+ ret -= mo->target->x;
+ ret += mo->target->y;
+ ret -= mo->target->z;
+ ret += mo->target->momx;
+ ret -= mo->target->momy;
+ ret += mo->target->momz;
+ ret -= mo->target->angle;
+ ret += mo->target->flags;
+ ret -= mo->target->flags2;
+ ret += mo->target->eflags;
+ ret -= mo->target->state - states;
+ ret += mo->target->tics;
+ ret -= mo->target->sprite;
+ ret += mo->target->frame;
+ }
+ else
+ ret ^= 0x3333;
+ if (mo->tracer && mo->tracer->type != MT_OVERLAY)
+ {
+ ret += mo->tracer->type;
+ ret -= mo->tracer->x;
+ ret += mo->tracer->y;
+ ret -= mo->tracer->z;
+ ret += mo->tracer->momx;
+ ret -= mo->tracer->momy;
+ ret += mo->tracer->momz;
+ ret -= mo->tracer->angle;
+ ret += mo->tracer->flags;
+ ret -= mo->tracer->flags2;
+ ret += mo->tracer->eflags;
+ ret -= mo->tracer->state - states;
+ ret += mo->tracer->tics;
+ ret -= mo->tracer->sprite;
+ ret += mo->tracer->frame;
+ }
+ else
+ ret ^= 0xAAAA;
+ ret -= mo->state - states;
+ ret += mo->tics;
+ ret -= mo->sprite;
+ ret += mo->frame;
+ }
+ }
+#endif
+
return (INT16)(ret & 0xFFFF);
}
@@ -3814,7 +4205,7 @@ static void CL_SendClientCmd(void)
if (gamestate == GS_WAITINGPLAYERS)
{
- // send NODEKEEPALIVE packet
+ // Send PT_NODEKEEPALIVE packet
netbuffer->packettype += 4;
packetsize = sizeof (clientcmd_pak) - sizeof (ticcmd_t) - sizeof (INT16);
HSendPacket(servernode, false, 0, packetsize);
@@ -3824,7 +4215,7 @@ static void CL_SendClientCmd(void)
G_MoveTiccmd(&netbuffer->u.clientpak.cmd, &localcmds, 1);
netbuffer->u.clientpak.consistancy = SHORT(consistancy[gametic%BACKUPTICS]);
- // send a special packet with 2 cmd for splitscreen
+ // Send a special packet with 2 cmd for splitscreen
if (splitscreen || botingame)
{
netbuffer->packettype += 2;
@@ -3837,25 +4228,25 @@ static void CL_SendClientCmd(void)
HSendPacket(servernode, false, 0, packetsize);
}
- if (cl_mode == cl_connected || dedicated)
+ if (cl_mode == CL_CONNECTED || dedicated)
{
- // send extra data if needed
+ // Send extra data if needed
if (localtextcmd[0])
{
netbuffer->packettype = PT_TEXTCMD;
M_Memcpy(netbuffer->u.textcmd,localtextcmd, localtextcmd[0]+1);
- // all extra data have been sended
- if (HSendPacket(servernode, true, 0, localtextcmd[0]+1)) // send can fail...
+ // All extra data have been sent
+ if (HSendPacket(servernode, true, 0, localtextcmd[0]+1)) // Send can fail...
localtextcmd[0] = 0;
}
- // send extra data if needed for player 2 (splitscreen)
+ // Send extra data if needed for player 2 (splitscreen)
if (localtextcmd2[0])
{
netbuffer->packettype = PT_TEXTCMD2;
M_Memcpy(netbuffer->u.textcmd, localtextcmd2, localtextcmd2[0]+1);
- // all extra data have been sended
- if (HSendPacket(servernode, true, 0, localtextcmd2[0]+1)) // send can fail...
+ // All extra data have been sent
+ if (HSendPacket(servernode, true, 0, localtextcmd2[0]+1)) // Send can fail...
localtextcmd2[0] = 0;
}
}
@@ -4119,7 +4510,7 @@ static inline void PingUpdate(void)
//check for ping limit breakage.
if (cv_maxping.value)
{
- for (i = 1; i < MAXNETNODES; i++)
+ for (i = 1; i < MAXPLAYERS; i++)
{
if (playeringame[i] && (realpingtable[i] / pingmeasurecount > (unsigned)cv_maxping.value))
{
@@ -4133,7 +4524,7 @@ static inline void PingUpdate(void)
//in that case, it is probably the server's fault.
if (numlaggers < D_NumPlayers() - 1)
{
- for (i = 1; i < MAXNETNODES; i++)
+ for (i = 1; i < MAXPLAYERS; i++)
{
if (playeringame[i] && laggers[i])
{
@@ -4148,7 +4539,7 @@ static inline void PingUpdate(void)
}
//make the ping packet and clear server data for next one
- for (i = 0; i < MAXNETNODES; i++)
+ for (i = 0; i < MAXPLAYERS; i++)
{
netbuffer->u.pingtable[i] = realpingtable[i] / pingmeasurecount;
//server takes a snapshot of the real ping for display.
@@ -4159,7 +4550,7 @@ static inline void PingUpdate(void)
//send out our ping packets
for (i = 0; i < MAXNETNODES; i++)
- if (playeringame[i])
+ if (nodeingame[i])
HSendPacket(i, true, 0, sizeof(INT32) * MAXPLAYERS);
pingmeasurecount = 1; //Reset count
@@ -4189,25 +4580,20 @@ void NetUpdate(void)
gametime = nowtime;
- if (!(gametime % 255) && netgame && server)
- {
-#ifdef NEWPING
- PingUpdate();
-#endif
- }
-
#ifdef NEWPING
if (server)
{
+ if (netgame && !(gametime % 255))
+ PingUpdate();
// update node latency values so we can take an average later.
- for (i = 0; i < MAXNETNODES; i++)
+ for (i = 0; i < MAXPLAYERS; i++)
if (playeringame[i])
- realpingtable[i] += G_TicsToMilliseconds(GetLag(i));
+ realpingtable[i] += G_TicsToMilliseconds(GetLag(playernode[i]));
pingmeasurecount++;
}
#endif
- if (!server)
+ if (client)
maketic = neededtic;
Local_Maketic(realtics); // make local tic, and call menu?
@@ -4220,12 +4606,12 @@ FILESTAMP
// client send the command after a receive of the server
// the server send before because in single player is beter
- MasterClient_Ticker(); // acking the master server
+ MasterClient_Ticker(); // Acking the Master Server
- if (!server)
+ if (client)
{
if (!resynch_local_inprogress)
- CL_SendClientCmd(); // send tic cmd
+ CL_SendClientCmd(); // Send tic cmd
hu_resynching = resynch_local_inprogress;
}
else
@@ -4251,27 +4637,32 @@ FILESTAMP
counts = -666;
}
- // do not make tics while resynching
+ // Do not make tics while resynching
if (counts != -666)
{
if (maketic + counts >= firstticstosend + BACKUPTICS)
counts = firstticstosend+BACKUPTICS-maketic-1;
for (i = 0; i < counts; i++)
- SV_Maketic(); // create missed tics and increment maketic
+ SV_Maketic(); // Create missed tics and increment maketic
- for (; tictoclear < firstticstosend; tictoclear++) // clear only when acknoledged
- D_Clearticcmd(tictoclear); // clear the maketic the new tic
+ for (; tictoclear < firstticstosend; tictoclear++) // Clear only when acknowledged
+ D_Clearticcmd(tictoclear); // Clear the maketic the new tic
SV_SendTics();
- neededtic = maketic; // the server is a client too
+ neededtic = maketic; // The server is a client too
}
else
hu_resynching = true;
}
}
Net_AckTicker();
+ // Handle timeouts to prevent definitive freezes from happenning
+ if (server)
+ for (i = 1; i < MAXNETNODES; i++)
+ if (nodeingame[i] && freezetimeout[i] < I_GetTime())
+ Net_ConnectionTimeout(i);
nowtime /= NEWTICRATERATIO;
if (nowtime > resptime)
{
@@ -4279,7 +4670,7 @@ FILESTAMP
M_Ticker();
CON_Ticker();
}
- FiletxTicker();
+ SV_FileSendTicker();
}
/** Returns the number of players playing.
diff --git a/src/d_clisrv.h b/src/d_clisrv.h
index f86c09b45..1ca82fdc5 100644
--- a/src/d_clisrv.h
+++ b/src/d_clisrv.h
@@ -59,7 +59,7 @@ typedef enum
// Add non-PT_CANFAIL packet types here to avoid breaking MS compatibility.
PT_CANFAIL, // This is kind of a priority. Anything bigger than CANFAIL
- // allows HSendPacket(,true,,) to return false.
+ // allows HSendPacket(*, true, *, *) to return false.
// In addition, this packet can't occupy all the available slots.
PT_FILEFRAGMENT = PT_CANFAIL, // A part of a file.
@@ -76,11 +76,19 @@ typedef enum
NUMPACKETTYPE
} packettype_t;
+#ifdef PACKETDROP
+void Command_Drop(void);
+void Command_Droprate(void);
+#endif
+#ifdef _DEBUG
+void Command_Numnodes(void);
+#endif
+
#if defined(_MSC_VER)
#pragma pack(1)
#endif
-// client to server packet
+// Client to server packet
typedef struct
{
UINT8 client_tic;
@@ -89,7 +97,7 @@ typedef struct
ticcmd_t cmd;
} ATTRPACK clientcmd_pak;
-// splitscreen packet
+// Splitscreen packet
// WARNING: must have the same format of clientcmd_pak, for more easy use
typedef struct
{
@@ -110,16 +118,16 @@ typedef struct
UINT8 starttic;
UINT8 numtics;
UINT8 numslots; // "Slots filled": Highest player number in use plus one.
- ticcmd_t cmds[45]; // normally [BACKUPTIC][MAXPLAYERS] but too large
+ ticcmd_t cmds[45]; // Normally [BACKUPTIC][MAXPLAYERS] but too large
} ATTRPACK servertics_pak;
-// sent to client when all consistency data
+// Sent to client when all consistency data
// for players has been restored
typedef struct
{
UINT32 randomseed;
- //ctf flag stuff
+ // CTF flag stuff
SINT8 flagplayer[2];
INT32 flagloose[2];
INT32 flagflags[2];
@@ -127,11 +135,11 @@ typedef struct
fixed_t flagy[2];
fixed_t flagz[2];
- UINT32 ingame; // spectator bit for each player
- UINT32 ctfteam; // if not spectator, then which team?
+ UINT32 ingame; // Spectator bit for each player
+ INT32 ctfteam[MAXPLAYERS]; // Which team? (can't be 1 bit, since in regular Match there are no teams)
// Resynch game scores and the like all at once
- UINT32 score[MAXPLAYERS]; // Everyone's score.
+ UINT32 score[MAXPLAYERS]; // Everyone's score
INT16 numboxes[MAXPLAYERS];
INT16 totalring[MAXPLAYERS];
tic_t realtime[MAXPLAYERS];
@@ -140,14 +148,14 @@ typedef struct
typedef struct
{
- //player stuff
+ // Player stuff
UINT8 playernum;
// Do not send anything visual related.
// Only send data that we need to know for physics.
- UINT8 playerstate; //playerstate_t
- UINT32 pflags; //pflags_t
- UINT8 panim; //panim_t
+ UINT8 playerstate; // playerstate_t
+ UINT32 pflags; // pflags_t
+ UINT8 panim; // panim_t
angle_t aiming;
INT32 currentweapon;
@@ -155,7 +163,7 @@ typedef struct
UINT16 powers[NUMPOWERS];
// Score is resynched in the confirm resync packet
- INT32 health;
+ INT32 rings;
SINT8 lives;
SINT8 continues;
UINT8 scoreadd;
@@ -164,6 +172,7 @@ typedef struct
UINT8 skincolor;
INT32 skin;
+ UINT32 availabilities;
// Just in case Lua does something like
// modify these at runtime
fixed_t camerascale;
@@ -176,9 +185,9 @@ typedef struct
UINT8 charability;
UINT8 charability2;
UINT32 charflags;
- UINT32 thokitem; //mobjtype_t
- UINT32 spinitem; //mobjtype_t
- UINT32 revitem; //mobjtype_t
+ UINT32 thokitem; // mobjtype_t
+ UINT32 spinitem; // mobjtype_t
+ UINT32 revitem; // mobjtype_t
fixed_t actionspd;
fixed_t mindash;
fixed_t maxdash;
@@ -187,7 +196,6 @@ typedef struct
fixed_t playerspinheight;
fixed_t speed;
- UINT8 jumping;
UINT8 secondjump;
UINT8 fly1;
tic_t glidetime;
@@ -234,8 +242,9 @@ typedef struct
INT32 onconveyor;
//player->mo stuff
- UINT8 hasmo; //boolean
+ UINT8 hasmo; // Boolean
+ INT32 health;
angle_t angle;
fixed_t x;
fixed_t y;
@@ -261,10 +270,10 @@ typedef struct
typedef struct
{
- UINT8 version; // different versions don't work
- UINT8 subversion; // contains build version
+ UINT8 version; // Different versions don't work
+ UINT8 subversion; // Contains build version
- // server launch stuffs
+ // Server launch stuffs
UINT8 serverplayer;
UINT8 totalslotnum; // "Slots": highest player number in use plus one.
@@ -275,21 +284,22 @@ typedef struct
// 0xFF == not in game; else player skin num
UINT8 playerskins[MAXPLAYERS];
UINT8 playercolor[MAXPLAYERS];
+ UINT32 playeravailabilities[MAXPLAYERS];
UINT8 gametype;
UINT8 modifiedgame;
- SINT8 adminplayer; // needs to be signed
+ SINT8 adminplayer; // Needs to be signed
- char server_context[8]; // unique context id, generated at server startup.
+ char server_context[8]; // Unique context id, generated at server startup.
- UINT8 varlengthinputs[0]; // playernames and netvars
+ UINT8 varlengthinputs[0]; // Playernames and netvars
} ATTRPACK serverconfig_pak;
typedef struct {
UINT8 fileid;
UINT32 position;
UINT16 size;
- UINT8 data[0]; // size is variable using hardware_MAXPACKETLENGTH
+ UINT8 data[0]; // Size is variable using hardware_MAXPACKETLENGTH
} ATTRPACK filetx_pak;
#ifdef _MSC_VER
@@ -298,14 +308,14 @@ typedef struct {
typedef struct
{
- UINT8 version; // different versions don't work
- UINT8 subversion; // contains build version
+ UINT8 version; // Different versions don't work
+ UINT8 subversion; // Contains build version
UINT8 localplayers;
UINT8 mode;
} ATTRPACK clientconfig_pak;
#define MAXSERVERNAME 32
-// this packet is too large
+// This packet is too large
typedef struct
{
UINT8 version;
@@ -371,45 +381,45 @@ typedef struct
} ATTRPACK plrconfig;
//
-// Network packet data.
+// Network packet data
//
typedef struct
{
UINT32 checksum;
- UINT8 ack; // if not null the node asks for acknowledgement, the receiver must resend the ack
- UINT8 ackreturn; // the return of the ack number
+ UINT8 ack; // If not zero the node asks for acknowledgement, the receiver must resend the ack
+ UINT8 ackreturn; // The return of the ack number
UINT8 packettype;
- UINT8 reserved; // padding
+ UINT8 reserved; // Padding
union
{
- clientcmd_pak clientpak; // 144 bytes
- client2cmd_pak client2pak; // 200 bytes
- servertics_pak serverpak; // 132495 bytes
- serverconfig_pak servercfg; // 773 bytes
- resynchend_pak resynchend; //
- resynch_pak resynchpak; //
- UINT8 resynchgot; //
- UINT8 textcmd[MAXTEXTCMD+1]; // 66049 bytes
- filetx_pak filetxpak; // 139 bytes
- clientconfig_pak clientcfg; // 136 bytes
- serverinfo_pak serverinfo; // 1024 bytes
- serverrefuse_pak serverrefuse; // 65025 bytes
- askinfo_pak askinfo; // 61 bytes
- msaskinfo_pak msaskinfo; // 22 bytes
- plrinfo playerinfo[MAXPLAYERS]; // 1152 bytes
- plrconfig playerconfig[MAXPLAYERS]; // (up to) 896 bytes
+ clientcmd_pak clientpak; // 144 bytes
+ client2cmd_pak client2pak; // 200 bytes
+ servertics_pak serverpak; // 132495 bytes (more around 360, no?)
+ serverconfig_pak servercfg; // 773 bytes
+ resynchend_pak resynchend; //
+ resynch_pak resynchpak; //
+ UINT8 resynchgot; //
+ UINT8 textcmd[MAXTEXTCMD+1]; // 66049 bytes (wut??? 64k??? More like 257 bytes...)
+ filetx_pak filetxpak; // 139 bytes
+ clientconfig_pak clientcfg; // 136 bytes
+ serverinfo_pak serverinfo; // 1024 bytes
+ serverrefuse_pak serverrefuse; // 65025 bytes (somehow I feel like those values are garbage...)
+ askinfo_pak askinfo; // 61 bytes
+ msaskinfo_pak msaskinfo; // 22 bytes
+ plrinfo playerinfo[MAXPLAYERS]; // 1152 bytes (I'd say 36~38)
+ plrconfig playerconfig[MAXPLAYERS]; // (up to) 896 bytes (welp they ARE)
#ifdef NEWPING
- UINT32 pingtable[MAXPLAYERS]; // 128 bytes
+ UINT32 pingtable[MAXPLAYERS]; // 128 bytes
#endif
- } u; // this is needed to pack diff packet types data together
+ } u; // This is needed to pack diff packet types data together
} ATTRPACK doomdata_t;
#if defined(_MSC_VER)
#pragma pack()
#endif
-#define MAXSERVERLIST 64 // depends only on the display
+#define MAXSERVERLIST 64 // Depends only on the display
typedef struct
{
SINT8 node;
@@ -420,7 +430,7 @@ extern serverelem_t serverlist[MAXSERVERLIST];
extern UINT32 serverlistcount;
extern INT32 mapchangepending;
-// points inside doomcom
+// Points inside doomcom
extern doomdata_t *netbuffer;
extern consvar_t cv_playbackspeed;
@@ -441,26 +451,28 @@ extern consvar_t cv_playbackspeed;
#define KICK_MSG_CUSTOM_BAN 8
extern boolean server;
-extern boolean dedicated; // for dedicated server
+#define client (!server)
+extern boolean dedicated; // For dedicated server
extern UINT16 software_MAXPACKETLENGTH;
extern boolean acceptnewnode;
extern SINT8 servernode;
void Command_Ping_f(void);
extern tic_t connectiontimeout;
+extern tic_t jointimeout;
#ifdef NEWPING
extern UINT16 pingmeasurecount;
extern UINT32 realpingtable[MAXPLAYERS];
extern UINT32 playerpingtable[MAXPLAYERS];
#endif
-extern consvar_t cv_joinnextround, cv_allownewplayer, cv_maxplayers, cv_resynchattempts, cv_blamecfail, cv_maxsend;
+extern consvar_t cv_joinnextround, cv_allownewplayer, cv_maxplayers, cv_resynchattempts, cv_blamecfail, cv_maxsend, cv_noticedownload, cv_downloadspeed;
-// used in d_net, the only dependence
+// Used in d_net, the only dependence
tic_t ExpandTics(INT32 low);
void D_ClientServerInit(void);
-// initialise the other field
+// Initialise the other field
void RegisterNetXCmd(netxcmd_t id, void (*cmd_f)(UINT8 **p, INT32 playernum));
void SendNetXCmd(netxcmd_t id, const void *param, size_t nparam);
void SendNetXCmd2(netxcmd_t id, const void *param, size_t nparam); // splitsreen player
@@ -478,14 +490,14 @@ void CL_RemoveSplitscreenPlayer(void);
void CL_Reset(void);
void CL_ClearPlayer(INT32 playernum);
void CL_UpdateServerList(boolean internetsearch, INT32 room);
-// is there a game running
+// Is there a game running
boolean Playing(void);
// Broadcasts special packets to other players
// to notify of game exit
void D_QuitNetGame(void);
-//? how many ticks to run?
+//? How many ticks to run?
void TryRunTics(tic_t realtic);
// extra data for lmps
diff --git a/src/d_main.c b/src/d_main.c
index 2ac72a9da..1a58ee89a 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -73,6 +73,7 @@ int snprintf(char *str, size_t n, const char *fmt, ...);
#include "dehacked.h" // Dehacked list test
#include "m_cond.h" // condition initialization
#include "fastcmp.h"
+#include "keys.h"
#ifdef CMAKECONFIG
#include "config.h"
@@ -106,8 +107,6 @@ UINT8 window_notinfocus = false;
//
// DEMO LOOP
//
-//static INT32 demosequence;
-static const char *pagename = "MAP1PIC";
static char *startupwadfiles[MAX_WADFILES];
boolean devparm = false; // started game with -devparm
@@ -176,6 +175,38 @@ void D_PostEvent(const event_t *ev)
void D_PostEvent_end(void) {};
#endif
+// modifier keys
+UINT8 shiftdown = 0; // 0x1 left, 0x2 right
+UINT8 ctrldown = 0; // 0x1 left, 0x2 right
+UINT8 altdown = 0; // 0x1 left, 0x2 right
+//
+// D_ModifierKeyResponder
+// Sets global shift/ctrl/alt variables, never actually eats events
+//
+static inline void D_ModifierKeyResponder(event_t *ev)
+{
+ if (ev->type == ev_keydown || ev->type == ev_console) switch (ev->data1)
+ {
+ case KEY_LSHIFT: shiftdown |= 0x1; return;
+ case KEY_RSHIFT: shiftdown |= 0x2; return;
+ case KEY_LCTRL: ctrldown |= 0x1; return;
+ case KEY_RCTRL: ctrldown |= 0x2; return;
+ case KEY_LALT: altdown |= 0x1; return;
+ case KEY_RALT: altdown |= 0x2; return;
+ default: return;
+ }
+ else if (ev->type == ev_keyup) switch (ev->data1)
+ {
+ case KEY_LSHIFT: shiftdown &= ~0x1; return;
+ case KEY_RSHIFT: shiftdown &= ~0x2; return;
+ case KEY_LCTRL: ctrldown &= ~0x1; return;
+ case KEY_RCTRL: ctrldown &= ~0x2; return;
+ case KEY_LALT: altdown &= ~0x1; return;
+ case KEY_RALT: altdown &= ~0x2; return;
+ default: return;
+ }
+}
+
//
// D_ProcessEvents
// Send all the events of the given timestamp down the responder chain
@@ -188,6 +219,9 @@ void D_ProcessEvents(void)
{
ev = &events[eventtail];
+ // Set global shift/ctrl/alt down variables
+ D_ModifierKeyResponder(ev); // never eats events
+
// Screenshots over everything so that they can be taken anywhere.
if (M_ScreenshotResponder(ev))
continue; // ate the event
@@ -684,9 +718,7 @@ void D_StartTitle(void)
maptol = 0;
gameaction = ga_nothing;
- playerdeadview = false;
displayplayer = consoleplayer = 0;
- //demosequence = -1;
gametype = GT_COOP;
paused = false;
advancedemo = false;
@@ -837,29 +869,24 @@ static void IdentifyVersion(void)
I_Error("File %s has been modified with non-music lumps",musicfile);
}
#endif
-}
-/* ======================================================================== */
-// Just print the nice red titlebar like the original SRB2 for DOS.
-/* ======================================================================== */
-#ifdef PC_DOS
-static inline void D_Titlebar(char *title1, char *title2)
-{
- // SRB2 banner
- clrscr();
- textattr((BLUE<<4)+WHITE);
- clreol();
- cputs(title1);
-
- // standard srb2 banner
- textattr((RED<<4)+WHITE);
- clreol();
- gotoxy((80-strlen(title2))/2, 2);
- cputs(title2);
- normvideo();
- gotoxy(1,3);
-}
+#if 1 // This section can be deleted when music_new is merged with music.dta
+ {
+ const char *musicfile = "music_new.dta";
+ const char *musicpath = va(pandf,srb2waddir,musicfile);
+ int ms = W_VerifyNMUSlumps(musicpath); // Don't forget the music!
+ if (ms == 1)
+ D_AddFile(musicpath);
+ else if (ms == 0)
+ I_Error("File %s has been modified with non-music lumps",musicfile);
+ }
#endif
+}
+
+#ifdef PC_DOS
+/* ======================================================================== */
+// Code for printing SRB2's title bar in DOS
+/* ======================================================================== */
//
// Center the title string, then add the date and time of compilation.
@@ -888,6 +915,31 @@ static inline void D_MakeTitleString(char *s)
strcpy(s, temp);
}
+static inline void D_Titlebar(void)
+{
+ char title1[82]; // srb2 title banner
+ char title2[82];
+
+ strcpy(title1, "Sonic Robo Blast 2");
+ strcpy(title2, "Sonic Robo Blast 2");
+
+ D_MakeTitleString(title1);
+
+ // SRB2 banner
+ clrscr();
+ textattr((BLUE<<4)+WHITE);
+ clreol();
+ cputs(title1);
+
+ // standard srb2 banner
+ textattr((RED<<4)+WHITE);
+ clreol();
+ gotoxy((80-strlen(title2))/2, 2);
+ cputs(title2);
+ normvideo();
+ gotoxy(1,3);
+}
+#endif
//
// D_SRB2Main
@@ -895,8 +947,6 @@ static inline void D_MakeTitleString(char *s)
void D_SRB2Main(void)
{
INT32 p;
- char srb2[82]; // srb2 title banner
- char title[82];
INT32 pstartmap = 1;
boolean autostart = false;
@@ -939,20 +989,8 @@ void D_SRB2Main(void)
dedicated = M_CheckParm("-dedicated") != 0;
#endif
- strcpy(title, "Sonic Robo Blast 2");
- strcpy(srb2, "Sonic Robo Blast 2");
- D_MakeTitleString(srb2);
-
#ifdef PC_DOS
- D_Titlebar(srb2, title);
-#endif
-
-#if defined (__OS2__) && !defined (HAVE_SDL)
- // set PM window title
- snprintf(pmData->title, sizeof (pmData->title),
- "Sonic Robo Blast 2" VERSIONSTRING ": %s",
- title);
- pmData->title[sizeof (pmData->title) - 1] = '\0';
+ D_Titlebar();
#endif
if (devparm)
@@ -1352,7 +1390,6 @@ void D_SRB2Main(void)
if (dedicated && server)
{
- pagename = "TITLESKY";
levelstarttic = gametic;
G_SetGamestate(GS_LEVEL);
if (!P_SetupLevel(false))
diff --git a/src/d_main.h b/src/d_main.h
index 6dc273b15..d73b19d1f 100644
--- a/src/d_main.h
+++ b/src/d_main.h
@@ -34,7 +34,7 @@ void D_SRB2Loop(void) FUNCNORETURN;
// D_SRB2Main()
// Not a globally visible function, just included for source reference,
// calls all startup code, parses command line options.
-// If not overrided by user input, calls N_AdvanceDemo.
+// If not overrided by user input, calls D_AdvanceDemo.
//
void D_SRB2Main(void);
@@ -51,9 +51,6 @@ const char *D_Home(void);
//
// BASE LEVEL
//
-void D_PageTicker(void);
-// pagename is lumpname of a 320x200 patch to fill the screen
-void D_PageDrawer(const char *pagename);
void D_AdvanceDemo(void);
void D_StartTitle(void);
diff --git a/src/d_net.c b/src/d_net.c
index 03e126b50..48c1d60ea 100644
--- a/src/d_net.c
+++ b/src/d_net.c
@@ -31,25 +31,27 @@
//
// NETWORKING
//
-// gametic is the tic about to be (or currently being) run
-// server:
+// gametic is the tic about to (or currently being) run
+// Server:
// maketic is the tic that hasn't had control made for it yet
-// nettics: is the tic for each node
-// firsttictosend: is the lowest value of nettics
-// client:
-// neededtic: is the tic needed by the client to run the game
-// firsttictosend: is used to optimize a condition
-// normally maketic >= gametic > 0
+// nettics is the tic for each node
+// firstticstosend is the lowest value of nettics
+// Client:
+// neededtic is the tic needed by the client to run the game
+// firstticstosend is used to optimize a condition
+// Normally maketic >= gametic > 0
#define FORCECLOSE 0x8000
-tic_t connectiontimeout = (15*TICRATE);
+tic_t connectiontimeout = (10*TICRATE);
/// \brief network packet
doomcom_t *doomcom = NULL;
/// \brief network packet data, points inside doomcom
doomdata_t *netbuffer = NULL;
+#ifdef DEBUGFILE
FILE *debugfile = NULL; // put some net info in a file during the game
+#endif
#define MAXREBOUND 8
static doomdata_t reboundstore[MAXREBOUND];
@@ -62,7 +64,7 @@ INT32 net_bandwidth;
/// \brief max length per packet
INT16 hardware_MAXPACKETLENGTH;
-void (*I_NetGet)(void) = NULL;
+boolean (*I_NetGet)(void) = NULL;
void (*I_NetSend)(void) = NULL;
boolean (*I_NetCanSend)(void) = NULL;
boolean (*I_NetCanGet)(void) = NULL;
@@ -129,9 +131,9 @@ boolean Net_GetNetStat(void)
// -----------------------------------------------------------------
// Some structs and functions for acknowledgement of packets
// -----------------------------------------------------------------
-#define MAXACKPACKETS 96 // minimum number of nodes
+#define MAXACKPACKETS 96 // Minimum number of nodes (wat)
#define MAXACKTOSEND 96
-#define URGENTFREESLOTENUM 10
+#define URGENTFREESLOTNUM 10
#define ACKTOSENDTIMEOUT (TICRATE/11)
#ifndef NONET
@@ -139,10 +141,10 @@ typedef struct
{
UINT8 acknum;
UINT8 nextacknum;
- UINT8 destinationnode;
- tic_t senttime;
- UINT16 length;
- UINT16 resentnum;
+ UINT8 destinationnode; // The node to send the ack to
+ tic_t senttime; // The time when the ack was sent
+ UINT16 length; // The packet size
+ UINT16 resentnum; // The number of times the ack has been resent
union {
SINT8 raw[MAXPACKETLENGTH];
doomdata_t data;
@@ -152,11 +154,12 @@ typedef struct
typedef enum
{
- CLOSE = 1, // flag is set when connection is closing
+ NF_CLOSE = 1, // Flag is set when connection is closing
+ NF_TIMEOUT = 2, // Flag is set when the node got a timeout
} node_flags_t;
#ifndef NONET
-// table of packet that was not acknowleged can be resend (the sender window)
+// Table of packets that were not acknowleged can be resent (the sender window)
static ackpak_t ackpak[MAXACKPACKETS];
#endif
@@ -212,11 +215,16 @@ FUNCMATH static INT32 cmpack(UINT8 a, UINT8 b)
return d;
}
-// return a free acknum and copy netbuffer in the ackpak table
+/** Sets freeack to a free acknum and copies the netbuffer in the ackpak table
+ *
+ * \param freeack The address to store the free acknum at
+ * \param lowtimer ???
+ * \return True if a free acknum was found
+ */
static boolean GetFreeAcknum(UINT8 *freeack, boolean lowtimer)
{
node_t *node = &nodes[doomcom->remotenode];
- INT32 i, numfreeslote = 0;
+ INT32 i, numfreeslot = 0;
if (cmpack((UINT8)((node->remotefirstack + MAXACKTOSEND) % 256), node->nextacknum) < 0)
{
@@ -227,10 +235,13 @@ static boolean GetFreeAcknum(UINT8 *freeack, boolean lowtimer)
for (i = 0; i < MAXACKPACKETS; i++)
if (!ackpak[i].acknum)
{
- // for low priority packet, make sure let freeslotes so urgents packets can be sent
- numfreeslote++;
- if (netbuffer->packettype >= PT_CANFAIL && numfreeslote < URGENTFREESLOTENUM)
- continue;
+ // For low priority packets, make sure to let freeslots so urgent packets can be sent
+ if (netbuffer->packettype >= PT_CANFAIL)
+ {
+ numfreeslot++;
+ if (numfreeslot <= URGENTFREESLOTNUM)
+ continue;
+ }
ackpak[i].acknum = node->nextacknum;
ackpak[i].nextacknum = node->nextacknum;
@@ -241,7 +252,7 @@ static boolean GetFreeAcknum(UINT8 *freeack, boolean lowtimer)
ackpak[i].length = doomcom->datalength;
if (lowtimer)
{
- // lowtime mean can't be sent now so try it soon as possible
+ // Lowtime means can't be sent now so try it as soon as possible
ackpak[i].senttime = 0;
ackpak[i].resentnum = 1;
}
@@ -254,7 +265,7 @@ static boolean GetFreeAcknum(UINT8 *freeack, boolean lowtimer)
*freeack = ackpak[i].acknum;
- sendackpacket++; // for stat
+ sendackpacket++; // For stat
return true;
}
@@ -266,14 +277,46 @@ static boolean GetFreeAcknum(UINT8 *freeack, boolean lowtimer)
return false;
}
-// Get a ack to send in the queu of this node
+/** Counts how many acks are free
+ *
+ * \param urgent True if the type of the packet meant to
+ * use an ack is lower than PT_CANFAIL
+ * If for some reason you don't want use it
+ * for any packet type in particular,
+ * just set to false
+ * \return The number of free acks
+ *
+ */
+INT32 Net_GetFreeAcks(boolean urgent)
+{
+ INT32 i, numfreeslot = 0;
+ INT32 n = 0; // Number of free acks found
+
+ for (i = 0; i < MAXACKPACKETS; i++)
+ if (!ackpak[i].acknum)
+ {
+ // For low priority packets, make sure to let freeslots so urgent packets can be sent
+ if (!urgent)
+ {
+ numfreeslot++;
+ if (numfreeslot <= URGENTFREESLOTNUM)
+ continue;
+ }
+
+ n++;
+ }
+
+ return n;
+}
+
+// Get a ack to send in the queue of this node
static UINT8 GetAcktosend(INT32 node)
{
nodes[node].lasttimeacktosend_sent = I_GetTime();
return nodes[node].firstacktosend;
}
-static void Removeack(INT32 i)
+static void RemoveAck(INT32 i)
{
INT32 node = ackpak[i].destinationnode;
#ifndef NEWPING
@@ -290,31 +333,31 @@ static void Removeack(INT32 i)
DEBFILE(va("Remove ack %d\n",ackpak[i].acknum));
#endif
ackpak[i].acknum = 0;
- if (nodes[node].flags & CLOSE)
+ if (nodes[node].flags & NF_CLOSE)
Net_CloseConnection(node);
}
-// we have got a packet proceed the ack request and ack return
+// We have got a packet, proceed the ack request and ack return
static boolean Processackpak(void)
{
INT32 i;
boolean goodpacket = true;
node_t *node = &nodes[doomcom->remotenode];
- // received an ack return, so remove the ack in the list
+ // Received an ack return, so remove the ack in the list
if (netbuffer->ackreturn && cmpack(node->remotefirstack, netbuffer->ackreturn) < 0)
{
node->remotefirstack = netbuffer->ackreturn;
- // search the ackbuffer and free it
+ // Search the ackbuffer and free it
for (i = 0; i < MAXACKPACKETS; i++)
if (ackpak[i].acknum && ackpak[i].destinationnode == node - nodes
&& cmpack(ackpak[i].acknum, netbuffer->ackreturn) <= 0)
{
- Removeack(i);
+ RemoveAck(i);
}
}
- // received a packet with ack, queue it to send the ack back
+ // Received a packet with ack, queue it to send the ack back
if (netbuffer->ack)
{
UINT8 ack = netbuffer->ack;
@@ -323,23 +366,23 @@ static boolean Processackpak(void)
{
DEBFILE(va("Discard(1) ack %d (duplicated)\n", ack));
duppacket++;
- goodpacket = false; // discard packet (duplicate)
+ goodpacket = false; // Discard packet (duplicate)
}
else
{
- // check if it is not already in the queue
+ // Check if it is not already in the queue
for (i = node->acktosend_tail; i != node->acktosend_head; i = (i+1) % MAXACKTOSEND)
if (node->acktosend[i] == ack)
{
DEBFILE(va("Discard(2) ack %d (duplicated)\n", ack));
duppacket++;
- goodpacket = false; // discard packet (duplicate)
+ goodpacket = false; // Discard packet (duplicate)
break;
}
if (goodpacket)
{
- // is a good packet so increment the acknowledge number,
- // then search for a "hole" in the queue
+ // Is a good packet so increment the acknowledge number,
+ // Then search for a "hole" in the queue
UINT8 nextfirstack = (UINT8)(node->firstacktosend + 1);
if (!nextfirstack)
nextfirstack = 1;
@@ -383,10 +426,10 @@ static boolean Processackpak(void)
}
}
}
- else // out of order packet
+ else // Out of order packet
{
- // don't increment firsacktosend, put it in asktosend queue
- // will be incremented when the nextfirstack comes (code above)
+ // Don't increment firsacktosend, put it in asktosend queue
+ // Will be incremented when the nextfirstack comes (code above)
UINT8 newhead = (UINT8)((node->acktosend_head+1) % MAXACKTOSEND);
DEBFILE(va("out of order packet (%d expected)\n", nextfirstack));
if (newhead != node->acktosend_tail)
@@ -394,8 +437,8 @@ static boolean Processackpak(void)
node->acktosend[node->acktosend_head] = ack;
node->acktosend_head = newhead;
}
- else // buffer full discard packet, sender will resend it
- { // we can admit the packet but we will not detect the duplication after :(
+ else // Buffer full discard packet, sender will resend it
+ { // We can admit the packet but we will not detect the duplication after :(
DEBFILE("no more freeackret\n");
goodpacket = false;
}
@@ -430,25 +473,29 @@ static void GotAcks(void)
if (ackpak[i].acknum && ackpak[i].destinationnode == doomcom->remotenode)
{
if (ackpak[i].acknum == netbuffer->u.textcmd[j])
- Removeack(i);
- else
- // nextacknum is first equal to acknum, then when receiving bigger ack
- // there is big chance the packet is lost
- // when resent, nextacknum = nodes[node].nextacknum
- // will redo the same but with different value
- if (cmpack(ackpak[i].nextacknum, netbuffer->u.textcmd[j]) <= 0
- && ackpak[i].senttime > 0)
- {
- ackpak[i].senttime--; // hurry up
- }
+ RemoveAck(i);
+ // nextacknum is first equal to acknum, then when receiving bigger ack
+ // there is big chance the packet is lost
+ // When resent, nextacknum = nodes[node].nextacknum
+ // will redo the same but with different value
+ else if (cmpack(ackpak[i].nextacknum, netbuffer->u.textcmd[j]) <= 0
+ && ackpak[i].senttime > 0)
+ {
+ ackpak[i].senttime--; // hurry up
+ }
}
}
#endif
-static inline void Net_ConnectionTimeout(INT32 node)
+void Net_ConnectionTimeout(INT32 node)
{
- // send a very special packet to self (hack the reboundstore queue)
- // main code will handle it
+ // Don't timeout several times
+ if (nodes[node].flags & NF_TIMEOUT)
+ return;
+ nodes[node].flags |= NF_TIMEOUT;
+
+ // Send a very special packet to self (hack the reboundstore queue)
+ // Main code will handle it
reboundstore[rebound_head].packettype = PT_NODETIMEOUT;
reboundstore[rebound_head].ack = 0;
reboundstore[rebound_head].ackreturn = 0;
@@ -456,12 +503,12 @@ static inline void Net_ConnectionTimeout(INT32 node)
reboundsize[rebound_head] = (INT16)(BASEPACKETSIZE + 1);
rebound_head = (rebound_head+1) % MAXREBOUND;
- // do not redo it quickly (if we do not close connection it is
+ // Do not redo it quickly (if we do not close connection it is
// for a good reason!)
nodes[node].lasttimepacketreceived = I_GetTime();
}
-// resend the data if needed
+// Resend the data if needed
void Net_AckTicker(void)
{
#ifndef NONET
@@ -477,7 +524,7 @@ void Net_AckTicker(void)
if (ackpak[i].acknum && ackpak[i].senttime + node->timeout < I_GetTime())
#endif
{
- if (ackpak[i].resentnum > 10 && (node->flags & CLOSE))
+ if (ackpak[i].resentnum > 10 && (node->flags & NF_CLOSE))
{
DEBFILE(va("ack %d sent 10 times so connection is supposed lost: node %d\n",
i, nodei));
@@ -497,7 +544,7 @@ void Net_AckTicker(void)
ackpak[i].senttime = I_GetTime();
ackpak[i].resentnum++;
ackpak[i].nextacknum = node->nextacknum;
- retransmit++; // for stat
+ retransmit++; // For stat
HSendPacket((INT32)(node - nodes), false, ackpak[i].acknum,
(size_t)(ackpak[i].length - BASEPACKETSIZE));
}
@@ -505,15 +552,15 @@ void Net_AckTicker(void)
for (i = 1; i < MAXNETNODES; i++)
{
- // this is something like node open flag
+ // This is something like node open flag
if (nodes[i].firstacktosend)
{
- // we haven't sent a packet for a long time
- // acknowledge packet if needed
+ // We haven't sent a packet for a long time
+ // Acknowledge packet if needed
if (nodes[i].lasttimeacktosend_sent + ACKTOSENDTIMEOUT < I_GetTime())
Net_SendAcks(i);
- if (!(nodes[i].flags & CLOSE)
+ if (!(nodes[i].flags & NF_CLOSE)
&& nodes[i].lasttimepacketreceived + connectiontimeout < I_GetTime())
{
Net_ConnectionTimeout(i);
@@ -523,9 +570,9 @@ void Net_AckTicker(void)
#endif
}
-// remove last packet received ack before resending the ackret
+// Remove last packet received ack before resending the ackreturn
// (the higher layer doesn't have room, or something else ....)
-void Net_UnAcknowledgPacket(INT32 node)
+void Net_UnAcknowledgePacket(INT32 node)
{
#ifdef NONET
(void)node;
@@ -564,20 +611,29 @@ void Net_UnAcknowledgPacket(INT32 node)
#endif
}
-boolean Net_AllAckReceived(void)
-{
#ifndef NONET
+/** Checks if all acks have been received
+ *
+ * \return True if all acks have been received
+ *
+ */
+static boolean Net_AllAcksReceived(void)
+{
INT32 i;
for (i = 0; i < MAXACKPACKETS; i++)
if (ackpak[i].acknum)
return false;
-#endif
return true;
}
+#endif
-// wait for all ackreturns with timeout in seconds
+/** Waits for all ackreturns
+ *
+ * \param timeout Timeout in seconds
+ *
+ */
void Net_WaitAllAckReceived(UINT32 timeout)
{
#ifdef NONET
@@ -587,7 +643,7 @@ void Net_WaitAllAckReceived(UINT32 timeout)
timeout = tictac + timeout*NEWTICRATE;
HGetPacket();
- while (timeout > I_GetTime() && !Net_AllAckReceived())
+ while (timeout > I_GetTime() && !Net_AllAcksReceived())
{
while (tictac == I_GetTime())
I_Sleep();
@@ -598,18 +654,18 @@ void Net_WaitAllAckReceived(UINT32 timeout)
#endif
}
-static void InitNode(INT32 node)
+static void InitNode(node_t *node)
{
- nodes[node].acktosend_head = nodes[node].acktosend_tail = 0;
+ node->acktosend_head = node->acktosend_tail = 0;
#ifndef NEWPING
- nodes[node].ping = PINGDEFAULT;
- nodes[node].varping = VARPINGDEFAULT;
- nodes[node].timeout = TIMEOUT(nodes[node].ping,nodes[node].varping);
+ node->ping = PINGDEFAULT;
+ node->varping = VARPINGDEFAULT;
+ node->timeout = TIMEOUT(node->ping, node->varping);
#endif
- nodes[node].firstacktosend = 0;
- nodes[node].nextacknum = 1;
- nodes[node].remotefirstack = 0;
- nodes[node].flags = 0;
+ node->firstacktosend = 0;
+ node->nextacknum = 1;
+ node->remotefirstack = 0;
+ node->flags = 0;
}
static void InitAck(void)
@@ -622,9 +678,14 @@ static void InitAck(void)
#endif
for (i = 0; i < MAXNETNODES; i++)
- InitNode(i);
+ InitNode(&nodes[i]);
}
+/** Removes all acks of a given packet type
+ *
+ * \param packettype The packet type to forget
+ *
+ */
void Net_AbortPacketType(UINT8 packettype)
{
#ifdef NONET
@@ -652,12 +713,25 @@ void Net_CloseConnection(INT32 node)
#else
INT32 i;
boolean forceclose = (node & FORCECLOSE) != 0;
+
+ if (node == -1)
+ {
+ DEBFILE(M_GetText("Net_CloseConnection: node -1 detected!\n"));
+ return; // nope, just ignore it
+ }
+
node &= ~FORCECLOSE;
if (!node)
return;
- nodes[node].flags |= CLOSE;
+ if (node < 0 || node >= MAXNETNODES) // prevent invalid nodes from crashing the game
+ {
+ DEBFILE(va(M_GetText("Net_CloseConnection: invalid node %d detected!\n"), node));
+ return;
+ }
+
+ nodes[node].flags |= NF_CLOSE;
// try to Send ack back (two army problem)
if (GetAcktosend(node))
@@ -676,8 +750,8 @@ void Net_CloseConnection(INT32 node)
ackpak[i].acknum = 0;
}
- InitNode(node);
- AbortSendFiles(node);
+ InitNode(&nodes[node]);
+ SV_AbortSendFiles(node);
I_NetFreeNodenum(node);
#endif
}
@@ -729,9 +803,15 @@ static void fprintfstring(char *s, size_t len)
}
if (mode)
fprintf(debugfile, "]");
+}
+
+static void fprintfstringnewline(char *s, size_t len)
+{
+ fprintfstring(s, len);
fprintf(debugfile, "\n");
}
+/// \warning Keep this up-to-date if you add/remove/rename packet types
static const char *packettypename[NUMPACKETTYPE] =
{
"NOTHING",
@@ -749,15 +829,22 @@ static const char *packettypename[NUMPACKETTYPE] =
"ASKINFO",
"SERVERINFO",
+ "PLAYERINFO",
"REQUESTFILE",
"ASKINFOVIAMS",
- "PLAYERCONFIGS",
+ "RESYNCHEND",
+ "RESYNCHGET",
+
"FILEFRAGMENT",
"TEXTCMD",
"TEXTCMD2",
"CLIENTJOIN",
"NODETIMEOUT",
+ "RESYNCHING",
+#ifdef NEWPING
+ "PING"
+#endif
};
static void DebugPrintpacket(const char *header)
@@ -770,20 +857,31 @@ static void DebugPrintpacket(const char *header)
{
case PT_ASKINFO:
case PT_ASKINFOVIAMS:
- fprintf(debugfile, " time %u\n", (tic_t)LONG(netbuffer->u.askinfo.time) );
+ fprintf(debugfile, " time %u\n", (tic_t)LONG(netbuffer->u.askinfo.time));
break;
case PT_CLIENTJOIN:
fprintf(debugfile, " number %d mode %d\n", netbuffer->u.clientcfg.localplayers,
netbuffer->u.clientcfg.mode);
break;
case PT_SERVERTICS:
+ {
+ servertics_pak *serverpak = &netbuffer->u.serverpak;
+ UINT8 *cmd = (UINT8 *)(&serverpak->cmds[serverpak->numslots * serverpak->numtics]);
+ size_t ntxtcmd = &((UINT8 *)netbuffer)[doomcom->datalength] - cmd;
+
fprintf(debugfile, " firsttic %u ply %d tics %d ntxtcmd %s\n ",
- (UINT32)ExpandTics(netbuffer->u.serverpak.starttic), netbuffer->u.serverpak.numslots,
- netbuffer->u.serverpak.numtics,
- sizeu1((size_t)(&((UINT8 *)netbuffer)[doomcom->datalength] - (UINT8 *)&netbuffer->u.serverpak.cmds[netbuffer->u.serverpak.numslots*netbuffer->u.serverpak.numtics])));
- fprintfstring((char *)&netbuffer->u.serverpak.cmds[netbuffer->u.serverpak.numslots*netbuffer->u.serverpak.numtics],(size_t)(
- &((UINT8 *)netbuffer)[doomcom->datalength] - (UINT8 *)&netbuffer->u.serverpak.cmds[netbuffer->u.serverpak.numslots*netbuffer->u.serverpak.numtics]));
+ (UINT32)ExpandTics(serverpak->starttic), serverpak->numslots, serverpak->numtics, sizeu1(ntxtcmd));
+ /// \todo Display more readable information about net commands
+ fprintfstringnewline((char *)cmd, ntxtcmd);
+ /*fprintfstring((char *)cmd, 3);
+ if (ntxtcmd > 4)
+ {
+ fprintf(debugfile, "[%s]", netxcmdnames[*((cmd) + 3) - 1]);
+ fprintfstring(((char *)cmd) + 4, ntxtcmd - 4);
+ }
+ fprintf(debugfile, "\n");*/
break;
+ }
case PT_CLIENTCMD:
case PT_CLIENT2CMD:
case PT_CLIENTMIS:
@@ -797,7 +895,8 @@ static void DebugPrintpacket(const char *header)
case PT_TEXTCMD:
case PT_TEXTCMD2:
fprintf(debugfile, " length %d\n ", netbuffer->u.textcmd[0]);
- fprintfstring((char *)netbuffer->u.textcmd+1, netbuffer->u.textcmd[0]);
+ fprintf(debugfile, "[%s]", netxcmdnames[netbuffer->u.textcmd[1] - 1]);
+ fprintfstringnewline((char *)netbuffer->u.textcmd + 2, netbuffer->u.textcmd[0] - 1);
break;
case PT_SERVERCFG:
fprintf(debugfile, " playerslots %d clientnode %d serverplayer %d "
@@ -813,7 +912,7 @@ static void DebugPrintpacket(const char *header)
netbuffer->u.serverinfo.maxplayer, netbuffer->u.serverinfo.mapname,
netbuffer->u.serverinfo.fileneedednum,
(UINT32)LONG(netbuffer->u.serverinfo.time));
- fprintfstring((char *)netbuffer->u.serverinfo.fileneeded,
+ fprintfstringnewline((char *)netbuffer->u.serverinfo.fileneeded,
(UINT8)((UINT8 *)netbuffer + doomcom->datalength
- (UINT8 *)netbuffer->u.serverinfo.fileneeded));
break;
@@ -827,20 +926,102 @@ static void DebugPrintpacket(const char *header)
break;
case PT_REQUESTFILE:
default: // write as a raw packet
- fprintfstring((char *)netbuffer->u.textcmd,
+ fprintfstringnewline((char *)netbuffer->u.textcmd,
(UINT8)((UINT8 *)netbuffer + doomcom->datalength - (UINT8 *)netbuffer->u.textcmd));
break;
}
}
#endif
+#ifdef PACKETDROP
+static INT32 packetdropquantity[NUMPACKETTYPE] = {0};
+static INT32 packetdroprate = 0;
+
+void Command_Drop(void)
+{
+ INT32 packetquantity;
+ const char *packetname;
+ size_t i;
+
+ if (COM_Argc() < 2)
+ {
+ CONS_Printf("drop [quantity]: drop packets\n"
+ "drop reset: cancel all packet drops\n");
+ return;
+ }
+
+ if (!(stricmp(COM_Argv(1), "reset") && stricmp(COM_Argv(1), "cancel") && stricmp(COM_Argv(1), "stop")))
+ {
+ memset(packetdropquantity, 0, sizeof(packetdropquantity));
+ return;
+ }
+
+ if (COM_Argc() >= 3)
+ {
+ packetquantity = atoi(COM_Argv(2));
+ if (packetquantity <= 0 && COM_Argv(2)[0] != '0')
+ {
+ CONS_Printf("Invalid quantity\n");
+ return;
+ }
+ }
+ else
+ packetquantity = -1;
+
+ packetname = COM_Argv(1);
+
+ if (!(stricmp(packetname, "all") && stricmp(packetname, "any")))
+ for (i = 0; i < NUMPACKETTYPE; i++)
+ packetdropquantity[i] = packetquantity;
+ else
+ {
+ for (i = 0; i < NUMPACKETTYPE; i++)
+ if (!stricmp(packetname, packettypename[i]))
+ {
+ packetdropquantity[i] = packetquantity;
+ return;
+ }
+
+ CONS_Printf("Unknown packet name\n");
+ }
+}
+
+void Command_Droprate(void)
+{
+ INT32 droprate;
+
+ if (COM_Argc() < 2)
+ {
+ CONS_Printf("Packet drop rate: %d%%\n", packetdroprate);
+ return;
+ }
+
+ droprate = atoi(COM_Argv(1));
+ if ((droprate <= 0 && COM_Argv(1)[0] != '0') || droprate > 100)
+ {
+ CONS_Printf("Packet drop rate must be between 0 and 100!\n");
+ return;
+ }
+
+ packetdroprate = droprate;
+}
+
+#ifndef NONET
+static boolean ShouldDropPacket(void)
+{
+ return (packetdropquantity[netbuffer->packettype])
+ || (packetdroprate != 0 && rand() < (RAND_MAX * (packetdroprate / 100.f))) || packetdroprate == 100;
+}
+#endif
+#endif
+
//
// HSendPacket
//
boolean HSendPacket(INT32 node, boolean reliable, UINT8 acknum, size_t packetlength)
{
doomcom->datalength = (INT16)(packetlength + BASEPACKETSIZE);
- if (node == 0) // packet is to go back to us
+ if (node == 0) // Packet is to go back to us
{
if ((rebound_head+1) % MAXREBOUND == rebound_tail)
{
@@ -871,7 +1052,7 @@ boolean HSendPacket(INT32 node, boolean reliable, UINT8 acknum, size_t packetlen
(void)reliable;
(void)acknum;
#else
- // do this before GetFreeAcknum because this function backup
+ // do this before GetFreeAcknum because this function backups
// the current packet
doomcom->remotenode = (INT16)node;
if (doomcom->datalength <= 0)
@@ -884,7 +1065,7 @@ boolean HSendPacket(INT32 node, boolean reliable, UINT8 acknum, size_t packetlen
return false;
}
- if (node < MAXNETNODES) // can be a broadcast
+ if (node < MAXNETNODES) // Can be a broadcast
netbuffer->ackreturn = GetAcktosend(node);
else
netbuffer->ackreturn = 0;
@@ -905,20 +1086,30 @@ boolean HSendPacket(INT32 node, boolean reliable, UINT8 acknum, size_t packetlen
netbuffer->ack = acknum;
netbuffer->checksum = NetbufferChecksum();
- sendbytes += packetheaderlength + doomcom->datalength; // for stat
+ sendbytes += packetheaderlength + doomcom->datalength; // For stat
- // simulate internet :)
- if (true || rand()<(INT32)RAND_MAX/5)
+#ifdef PACKETDROP
+ // Simulate internet :)
+ //if (rand() >= (INT32)(RAND_MAX * (PACKETLOSSRATE / 100.f)))
+ if (!ShouldDropPacket())
{
+#endif
#ifdef DEBUGFILE
if (debugfile)
- DebugPrintpacket("SEND");
+ DebugPrintpacket("SENT");
#endif
I_NetSend();
+#ifdef PACKETDROP
}
+ else
+ {
+ if (packetdropquantity[netbuffer->packettype] > 0)
+ packetdropquantity[netbuffer->packettype]--;
#ifdef DEBUGFILE
- else if (debugfile)
- DebugPrintpacket("NOTSEND");
+ if (debugfile)
+ DebugPrintpacket("NOT SENT");
+#endif
+ }
#endif
#endif // ndef NONET
@@ -933,7 +1124,9 @@ boolean HSendPacket(INT32 node, boolean reliable, UINT8 acknum, size_t packetlen
//
boolean HGetPacket(void)
{
- // get a packet from self
+ //boolean nodejustjoined;
+
+ // Get a packet from self
if (rebound_tail != rebound_head)
{
M_Memcpy(netbuffer, &reboundstore[rebound_tail], reboundsize[rebound_tail]);
@@ -958,16 +1151,17 @@ boolean HGetPacket(void)
while(true)
{
+ //nodejustjoined = I_NetGet();
I_NetGet();
- if (doomcom->remotenode == -1)
+ if (doomcom->remotenode == -1) // No packet received
return false;
- getbytes += packetheaderlength + doomcom->datalength; // for stat
+ getbytes += packetheaderlength + doomcom->datalength; // For stat
if (doomcom->remotenode >= MAXNETNODES)
{
- DEBFILE(va("receive packet from node %d !\n", doomcom->remotenode));
+ DEBFILE(va("Received packet from node %d!\n", doomcom->remotenode));
continue;
}
@@ -976,6 +1170,7 @@ boolean HGetPacket(void)
if (netbuffer->checksum != NetbufferChecksum())
{
DEBFILE("Bad packet checksum\n");
+ //Net_CloseConnection(nodejustjoined ? (doomcom->remotenode | FORCECLOSE) : doomcom->remotenode);
Net_CloseConnection(doomcom->remotenode);
continue;
}
@@ -985,11 +1180,26 @@ boolean HGetPacket(void)
DebugPrintpacket("GET");
#endif
- // proceed the ack and ackreturn field
+ /*// If a new node sends an unexpected packet, just ignore it
+ if (nodejustjoined && server
+ && !(netbuffer->packettype == PT_ASKINFO
+ || netbuffer->packettype == PT_SERVERINFO
+ || netbuffer->packettype == PT_PLAYERINFO
+ || netbuffer->packettype == PT_REQUESTFILE
+ || netbuffer->packettype == PT_ASKINFOVIAMS
+ || netbuffer->packettype == PT_CLIENTJOIN))
+ {
+ DEBFILE(va("New node sent an unexpected %s packet\n", packettypename[netbuffer->packettype]));
+ //CONS_Alert(CONS_NOTICE, "New node sent an unexpected %s packet\n", packettypename[netbuffer->packettype]);
+ Net_CloseConnection(doomcom->remotenode | FORCECLOSE);
+ continue;
+ }*/
+
+ // Proceed the ack and ackreturn field
if (!Processackpak())
continue; // discarded (duplicated)
- // a packet with just ackreturn
+ // A packet with just ackreturn
if (netbuffer->packettype == PT_NOTHING)
{
GotAcks();
@@ -1002,9 +1212,10 @@ boolean HGetPacket(void)
return true;
}
-static void Internal_Get(void)
+static boolean Internal_Get(void)
{
doomcom->remotenode = -1;
+ return false;
}
FUNCNORETURN static ATTRNORETURN void Internal_Send(void)
@@ -1089,7 +1300,7 @@ boolean D_CheckNetGame(void)
if (netgame)
ret = true;
- if (!server && netgame)
+ if (client && netgame)
netgame = false;
server = true; // WTF? server always true???
// no! The deault mode is server. Client is set elsewhere
@@ -1230,4 +1441,6 @@ void D_CloseConnection(void)
netgame = false;
addedtogame = false;
}
+
+ D_ResetTiccmds();
}
diff --git a/src/d_net.h b/src/d_net.h
index 285b44235..84814ce39 100644
--- a/src/d_net.h
+++ b/src/d_net.h
@@ -18,10 +18,10 @@
#ifndef __D_NET__
#define __D_NET__
-// Max computers in a game.
+// Max computers in a game
#define MAXNETNODES 32
#define BROADCASTADDR MAXNETNODES
-#define MAXSPLITSCREENPLAYERS 2 // max number of players on a single computer
+#define MAXSPLITSCREENPLAYERS 2 // Max number of players on a single computer
#define STATLENGTH (TICRATE*2)
@@ -32,17 +32,17 @@ extern float lostpercent, duppercent, gamelostpercent;
extern INT32 packetheaderlength;
boolean Net_GetNetStat(void);
extern INT32 getbytes;
-extern INT64 sendbytes; // realtime updated
+extern INT64 sendbytes; // Realtime updated
extern SINT8 nodetoplayer[MAXNETNODES];
-extern SINT8 nodetoplayer2[MAXNETNODES]; // say the numplayer for this node if any (splitscreen)
-extern UINT8 playerpernode[MAXNETNODES]; // used specialy for scplitscreen
-extern boolean nodeingame[MAXNETNODES]; // set false as nodes leave game
+extern SINT8 nodetoplayer2[MAXNETNODES]; // Say the numplayer for this node if any (splitscreen)
+extern UINT8 playerpernode[MAXNETNODES]; // Used specially for splitscreen
+extern boolean nodeingame[MAXNETNODES]; // Set false as nodes leave game
+INT32 Net_GetFreeAcks(boolean urgent);
void Net_AckTicker(void);
-boolean Net_AllAckReceived(void);
-// if reliable return true if packet sent, 0 else
+// If reliable return true if packet sent, 0 else
boolean HSendPacket(INT32 node, boolean reliable, UINT8 acknum,
size_t packetlength);
boolean HGetPacket(void);
@@ -52,9 +52,11 @@ void D_SaveBan(void);
#endif
boolean D_CheckNetGame(void);
void D_CloseConnection(void);
-void Net_UnAcknowledgPacket(INT32 node);
+void Net_UnAcknowledgePacket(INT32 node);
void Net_CloseConnection(INT32 node);
+void Net_ConnectionTimeout(INT32 node);
void Net_AbortPacketType(UINT8 packettype);
void Net_SendAcks(INT32 node);
void Net_WaitAllAckReceived(UINT32 timeout);
+
#endif
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 65b415e65..0e96b0912 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -82,6 +82,7 @@ static void AutoBalance_OnChange(void);
static void TeamScramble_OnChange(void);
static void NetTimeout_OnChange(void);
+static void JoinTimeout_OnChange(void);
static void Ringslinger_OnChange(void);
static void Gravity_OnChange(void);
@@ -188,14 +189,13 @@ static CV_PossibleValue_t teamscramble_cons_t[] = {{0, "Off"}, {1, "Random"}, {2
static CV_PossibleValue_t startingliveslimit_cons_t[] = {{1, "MIN"}, {99, "MAX"}, {0, NULL}};
static CV_PossibleValue_t sleeping_cons_t[] = {{-1, "MIN"}, {1000/TICRATE, "MAX"}, {0, NULL}};
-static CV_PossibleValue_t competitionboxes_cons_t[] = {{0, "Normal"}, {1, "Random"}, {2, "Teleports"},
+static CV_PossibleValue_t competitionboxes_cons_t[] = {{0, "Normal"}, {1, "Random"}, //{2, "Teleports"},
{3, "None"}, {0, NULL}};
static CV_PossibleValue_t matchboxes_cons_t[] = {{0, "Normal"}, {1, "Random"}, {2, "Non-Random"},
{3, "None"}, {0, NULL}};
static CV_PossibleValue_t chances_cons_t[] = {{0, "MIN"}, {9, "MAX"}, {0, NULL}};
-static CV_PossibleValue_t match_scoring_cons_t[] = {{0, "Normal"}, {1, "Classic"}, {0, NULL}};
static CV_PossibleValue_t pause_cons_t[] = {{0, "Server"}, {1, "All"}, {0, NULL}};
static CV_PossibleValue_t timetic_cons_t[] = {{0, "Normal"}, {1, "Tics"}, {2, "Centiseconds"}, {0, NULL}};
@@ -310,7 +310,6 @@ consvar_t cv_friendlyfire = {"friendlyfire", "Off", CV_NETVAR, CV_OnOff, NULL, 0
consvar_t cv_itemfinder = {"itemfinder", "Off", CV_CALL, CV_OnOff, ItemFinder_OnChange, 0, NULL, NULL, 0, 0, NULL};
// Scoring type options
-consvar_t cv_match_scoring = {"matchscoring", "Normal", CV_NETVAR|CV_CHEAT, match_scoring_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
consvar_t cv_overtime = {"overtime", "Yes", CV_NETVAR, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
consvar_t cv_rollingdemos = {"rollingdemos", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
@@ -340,7 +339,9 @@ consvar_t cv_killingdead = {"killingdead", "Off", CV_NETVAR, CV_OnOff, NULL, 0,
consvar_t cv_netstat = {"netstat", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; // show bandwidth statistics
static CV_PossibleValue_t nettimeout_cons_t[] = {{TICRATE/7, "MIN"}, {60*TICRATE, "MAX"}, {0, NULL}};
-consvar_t cv_nettimeout = {"nettimeout", "525", CV_CALL|CV_SAVE, nettimeout_cons_t, NetTimeout_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_nettimeout = {"nettimeout", "350", CV_CALL|CV_SAVE, nettimeout_cons_t, NetTimeout_OnChange, 0, NULL, NULL, 0, 0, NULL};
+static CV_PossibleValue_t jointimeout_cons_t[] = {{5*TICRATE, "MIN"}, {60*TICRATE, "MAX"}, {0, NULL}};
+consvar_t cv_jointimeout = {"jointimeout", "350", CV_CALL|CV_SAVE, jointimeout_cons_t, JoinTimeout_OnChange, 0, NULL, NULL, 0, 0, NULL};
#ifdef NEWPING
consvar_t cv_maxping = {"maxping", "0", CV_SAVE, CV_Unsigned, NULL, 0, NULL, NULL, 0, 0, NULL};
#endif
@@ -365,6 +366,35 @@ boolean splitscreen = false;
boolean circuitmap = false;
INT32 adminplayer = -1;
+/// \warning Keep this up-to-date if you add/remove/rename net text commands
+const char *netxcmdnames[MAXNETXCMD - 1] =
+{
+ "NAMEANDCOLOR",
+ "WEAPONPREF",
+ "KICK",
+ "NETVAR",
+ "SAY",
+ "MAP",
+ "EXITLEVEL",
+ "ADDFILE",
+ "PAUSE",
+ "ADDPLAYER",
+ "TEAMCHANGE",
+ "CLEARSCORES",
+ "LOGIN",
+ "VERIFIED",
+ "RANDOMSEED",
+ "RUNSOC",
+ "REQADDFILE",
+ "DELFILE",
+ "SETMOTD",
+ "SUICIDE",
+#ifdef HAVE_BLUA
+ "LUACMD",
+ "LUAVAR"
+#endif
+};
+
// =========================================================================
// SERVER STARTUP
// =========================================================================
@@ -453,7 +483,6 @@ void D_RegisterServerCommands(void)
CV_RegisterVar(&cv_itemrespawntime);
CV_RegisterVar(&cv_itemrespawn);
CV_RegisterVar(&cv_flagtime);
- CV_RegisterVar(&cv_suddendeath);
// misc
CV_RegisterVar(&cv_friendlyfire);
@@ -501,7 +530,6 @@ void D_RegisterServerCommands(void)
CV_RegisterVar(&cv_startinglives);
CV_RegisterVar(&cv_countdowntime);
CV_RegisterVar(&cv_runscripts);
- CV_RegisterVar(&cv_match_scoring);
CV_RegisterVar(&cv_overtime);
CV_RegisterVar(&cv_pause);
CV_RegisterVar(&cv_mute);
@@ -517,9 +545,12 @@ void D_RegisterServerCommands(void)
// d_clisrv
CV_RegisterVar(&cv_maxplayers);
CV_RegisterVar(&cv_maxsend);
+ CV_RegisterVar(&cv_noticedownload);
+ CV_RegisterVar(&cv_downloadspeed);
COM_AddCommand("ping", Command_Ping_f);
CV_RegisterVar(&cv_nettimeout);
+ CV_RegisterVar(&cv_jointimeout);
CV_RegisterVar(&cv_skipmapcheck);
CV_RegisterVar(&cv_sleep);
@@ -581,6 +612,7 @@ void D_RegisterClientCommands(void)
CV_RegisterVar(&cv_screenshot_option);
CV_RegisterVar(&cv_screenshot_folder);
+ CV_RegisterVar(&cv_screenshot_colorprofile);
CV_RegisterVar(&cv_moviemode);
// PNG variables
CV_RegisterVar(&cv_zlib_level);
@@ -638,7 +670,29 @@ void D_RegisterClientCommands(void)
CV_RegisterVar(&cv_resetmusic);
// FIXME: not to be here.. but needs be done for config loading
- CV_RegisterVar(&cv_usegamma);
+ CV_RegisterVar(&cv_globalgamma);
+ CV_RegisterVar(&cv_globalsaturation);
+
+ CV_RegisterVar(&cv_rhue);
+ CV_RegisterVar(&cv_yhue);
+ CV_RegisterVar(&cv_ghue);
+ CV_RegisterVar(&cv_chue);
+ CV_RegisterVar(&cv_bhue);
+ CV_RegisterVar(&cv_mhue);
+
+ CV_RegisterVar(&cv_rgamma);
+ CV_RegisterVar(&cv_ygamma);
+ CV_RegisterVar(&cv_ggamma);
+ CV_RegisterVar(&cv_cgamma);
+ CV_RegisterVar(&cv_bgamma);
+ CV_RegisterVar(&cv_mgamma);
+
+ CV_RegisterVar(&cv_rsaturation);
+ CV_RegisterVar(&cv_ysaturation);
+ CV_RegisterVar(&cv_gsaturation);
+ CV_RegisterVar(&cv_csaturation);
+ CV_RegisterVar(&cv_bsaturation);
+ CV_RegisterVar(&cv_msaturation);
// m_menu.c
CV_RegisterVar(&cv_crosshair);
@@ -696,6 +750,7 @@ void D_RegisterClientCommands(void)
// s_sound.c
CV_RegisterVar(&cv_soundvolume);
+ CV_RegisterVar(&cv_closedcaptioning);
CV_RegisterVar(&cv_digmusicvolume);
CV_RegisterVar(&cv_midimusicvolume);
CV_RegisterVar(&cv_numChannels);
@@ -976,7 +1031,7 @@ UINT8 CanChangeSkin(INT32 playernum)
return true;
// Force skin in effect.
- if (!server && (cv_forceskin.value != -1) && !(adminplayer == playernum && serverplayer == -1))
+ if ((cv_forceskin.value != -1) || (mapheaderinfo[gamemap-1] && mapheaderinfo[gamemap-1]->forcecharacter[0] != '\0'))
return false;
// Can change skin in intermission and whatnot.
@@ -1074,6 +1129,8 @@ static void SendNameAndColor(void)
if (!Playing())
return;
+ players[consoleplayer].availabilities = R_GetSkinAvailabilities();
+
// If you're not in a netgame, merely update the skin, color, and name.
if (!netgame)
{
@@ -1092,7 +1149,7 @@ static void SendNameAndColor(void)
SetPlayerSkinByNum(consoleplayer, 0);
CV_StealthSet(&cv_skin, skins[0].name);
}
- else if ((foundskin = R_SkinAvailable(cv_skin.string)) != -1 && R_SkinUnlock(foundskin))
+ else if ((foundskin = R_SkinAvailable(cv_skin.string)) != -1 && R_SkinUsable(consoleplayer, foundskin))
{
boolean notsame;
@@ -1139,7 +1196,7 @@ static void SendNameAndColor(void)
// check if player has the skin loaded (cv_skin may have
// the name of a skin that was available in the previous game)
cv_skin.value = R_SkinAvailable(cv_skin.string);
- if ((cv_skin.value < 0) || !R_SkinUnlock(cv_skin.value))
+ if ((cv_skin.value < 0) || !R_SkinUsable(consoleplayer, cv_skin.value))
{
CV_StealthSet(&cv_skin, DEFAULTSKIN);
cv_skin.value = 0;
@@ -1147,6 +1204,7 @@ static void SendNameAndColor(void)
// Finally write out the complete packet and send it off.
WRITESTRINGN(p, cv_playername.zstring, MAXPLAYERNAME);
+ WRITEUINT32(p, (UINT32)players[consoleplayer].availabilities);
WRITEUINT8(p, (UINT8)cv_playercolor.value);
WRITEUINT8(p, (UINT8)cv_skin.value);
SendNetXCmd(XD_NAMEANDCOLOR, buf, p - buf);
@@ -1189,6 +1247,8 @@ static void SendNameAndColor2(void)
if (!Playing())
return;
+ players[secondplaya].availabilities = R_GetSkinAvailabilities();
+
// If you're not in a netgame, merely update the skin, color, and name.
if (botingame)
{
@@ -1217,7 +1277,7 @@ static void SendNameAndColor2(void)
SetPlayerSkinByNum(secondplaya, forcedskin);
CV_StealthSet(&cv_skin2, skins[forcedskin].name);
}
- else if ((foundskin = R_SkinAvailable(cv_skin2.string)) != -1 && R_SkinUnlock(foundskin))
+ else if ((foundskin = R_SkinAvailable(cv_skin2.string)) != -1 && R_SkinUsable(secondplaya, foundskin))
{
boolean notsame;
@@ -1272,6 +1332,7 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum)
#endif
READSTRINGN(*cp, name, MAXPLAYERNAME);
+ p->availabilities = READUINT32(*cp);
color = READUINT8(*cp);
skin = READUINT8(*cp);
@@ -1288,6 +1349,7 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum)
if (server && (p != &players[consoleplayer] && p != &players[secondarydisplayplayer]))
{
boolean kick = false;
+ INT32 s;
// team colors
if (G_GametypeHasTeams())
@@ -1302,6 +1364,16 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum)
if (!p->skincolor)
kick = true;
+ // availabilities
+ for (s = 0; s < MAXSKINS; s++)
+ {
+ if (!skins[s].availability && (p->availabilities & (1 << s)))
+ {
+ kick = true;
+ break;
+ }
+ }
+
if (kick)
{
XBOXSTATIC UINT8 buf[2];
@@ -1497,10 +1569,13 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pultmode, boolean rese
// The supplied data are assumed to be good.
I_Assert(delay >= 0 && delay <= 2);
+ if (mapnum != -1)
+ CV_SetValue(&cv_nextmap, mapnum);
+
CONS_Debug(DBG_GAMELOGIC, "Map change: mapnum=%d gametype=%d ultmode=%d resetplayers=%d delay=%d skipprecutscene=%d\n",
mapnum, newgametype, pultmode, resetplayers, delay, skipprecutscene);
- if (netgame || multiplayer)
+ if ((netgame || multiplayer) && !((gametype == newgametype) && (newgametype == GT_COOP)))
FLS = false;
if (delay != 2)
@@ -1534,8 +1609,13 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pultmode, boolean rese
mapchangepending = 0;
// spawn the server if needed
// reset players if there is a new one
- if (!(adminplayer == consoleplayer) && SV_SpawnServer())
- buf[0] &= ~(1<<1);
+ if (!(adminplayer == consoleplayer))
+ {
+ if (SV_SpawnServer())
+ buf[0] &= ~(1<<1);
+ if (!Playing()) // you failed to start a server somehow, so cancel the map change
+ return;
+ }
// Kick bot from special stages
if (botskin)
@@ -1587,7 +1667,7 @@ static void Command_Map_f(void)
return;
}
- if (!server && !(adminplayer == consoleplayer))
+ if (client && !(adminplayer == consoleplayer))
{
CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
return;
@@ -1666,9 +1746,19 @@ static void Command_Map_f(void)
}
}
+ // Prevent warping to locked levels
+ // ... unless you're in a dedicated server. Yes, technically this means you can view any level by
+ // running a dedicated server and joining it yourself, but that's better than making dedicated server's
+ // lives hell.
+ if (!dedicated && M_MapLocked(newmapnum))
+ {
+ CONS_Alert(CONS_NOTICE, M_GetText("You need to unlock this level before you can warp to it!\n"));
+ return;
+ }
+
// don't use a gametype the map doesn't support
if (cv_debug || COM_CheckParm("-force") || cv_skipmapcheck.value)
- ; // The player wants us to trek on anyway. Do so.
+ fromlevelselect = false; // The player wants us to trek on anyway. Do so.
// G_TOLFlag handles both multiplayer gametype and ignores it for !multiplayer
// Alternatively, bail if the map header is completely missing anyway.
else if (!mapheaderinfo[newmapnum-1]
@@ -1687,19 +1777,10 @@ static void Command_Map_f(void)
CONS_Alert(CONS_WARNING, M_GetText("%s doesn't support %s mode!\n(Use -force to override)\n"), mapname, gametypestring);
return;
}
+ else
+ fromlevelselect = ((netgame || multiplayer) && ((gametype == newgametype) && (newgametype == GT_COOP)));
- // Prevent warping to locked levels
- // ... unless you're in a dedicated server. Yes, technically this means you can view any level by
- // running a dedicated server and joining it yourself, but that's better than making dedicated server's
- // lives hell.
- if (!dedicated && M_MapLocked(newmapnum))
- {
- CONS_Alert(CONS_NOTICE, M_GetText("You need to unlock this level before you can warp to it!\n"));
- return;
- }
-
- fromlevelselect = false;
- D_MapChange(newmapnum, newgametype, false, newresetplayers, 0, false, false);
+ D_MapChange(newmapnum, newgametype, false, newresetplayers, 0, false, fromlevelselect);
}
/** Receives a map command and changes the map.
@@ -1765,17 +1846,14 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum)
if (demoplayback && !timingdemo)
precache = false;
- if (resetplayer)
- {
- if (!FLS || (netgame || multiplayer))
- emeralds = 0;
- }
+ if (resetplayer && !FLS)
+ emeralds = 0;
#ifdef HAVE_BLUA
LUAh_MapChange();
#endif
- G_InitNew(ultimatemode, mapname, resetplayer, skipprecutscene);
+ G_InitNew(ultimatemode, mapname, resetplayer, skipprecutscene, FLS);
if (demoplayback && !timingdemo)
precache = true;
CON_ToggleOff();
@@ -1914,7 +1992,7 @@ static void Got_Suicide(UINT8 **cp, INT32 playernum)
// You can't suicide someone else. Nice try, there.
if (suicideplayer != playernum || (!G_PlatformGametype()))
{
- CONS_Alert(CONS_WARNING, M_GetText("Illegal suicide command recieved from %s\n"), player_names[playernum]);
+ CONS_Alert(CONS_WARNING, M_GetText("Illegal suicide command received from %s\n"), player_names[playernum]);
if (server)
{
XBOXSTATIC UINT8 buf[2];
@@ -2081,7 +2159,7 @@ static void Command_Teamchange_f(void)
return;
}
- if (!cv_allowteamchange.value && !NetPacket.packet.newteam) // allow swapping to spectator even in locked teams.
+ if (!cv_allowteamchange.value && NetPacket.packet.newteam) // allow swapping to spectator even in locked teams.
{
CONS_Alert(CONS_NOTICE, M_GetText("The server is not allowing team changes at the moment.\n"));
return;
@@ -2178,7 +2256,7 @@ static void Command_Teamchange2_f(void)
return;
}
- if (!cv_allowteamchange.value && !NetPacket.packet.newteam) // allow swapping to spectator even in locked teams.
+ if (!cv_allowteamchange.value && NetPacket.packet.newteam) // allow swapping to spectator even in locked teams.
{
CONS_Alert(CONS_NOTICE, M_GetText("The server is not allowing team changes at the moment.\n"));
return;
@@ -2574,7 +2652,7 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum)
if (players[playernum].spectator)
{
players[playernum].score = 0;
- players[playernum].health = 1;
+ players[playernum].rings = 0;
if (players[playernum].mo)
players[playernum].mo->health = 1;
}
@@ -2629,7 +2707,7 @@ static void Command_Changepassword_f(void)
// If we have no MD5 support then completely disable XD_LOGIN responses for security.
CONS_Alert(CONS_NOTICE, "Remote administration commands are not supported in this build.\n");
#else
- if (!server) // cannot change remotely
+ if (client) // cannot change remotely
{
CONS_Printf(M_GetText("Only the server can use this.\n"));
return;
@@ -2688,7 +2766,7 @@ static void Got_Login(UINT8 **cp, INT32 playernum)
READMEM(*cp, sentmd5, 16);
- if (!server)
+ if (client)
return;
// Do the final pass to compare with the sent md5
@@ -2710,7 +2788,7 @@ static void Command_Verify_f(void)
char *temp;
INT32 playernum;
- if (!server)
+ if (client)
{
CONS_Printf(M_GetText("Only the server can use this.\n"));
return;
@@ -2794,7 +2872,7 @@ static void Command_MotD_f(void)
return;
}
- if ((netgame || multiplayer) && !server)
+ if ((netgame || multiplayer) && client)
SendNetXCmd(XD_SETMOTD, mymotd, sizeof(motd));
else
{
@@ -2932,6 +3010,7 @@ static void Command_Addfile(void)
XBOXSTATIC char buf[256];
char *buf_p = buf;
INT32 i;
+ int musiconly; // W_VerifyNMUSlumps isn't boolean
if (COM_Argc() != 2)
{
@@ -2946,7 +3025,9 @@ static void Command_Addfile(void)
if (!isprint(fn[i]) || fn[i] == ';')
return;
- if (!W_VerifyNMUSlumps(fn))
+ musiconly = W_VerifyNMUSlumps(fn);
+
+ if (!musiconly)
{
// ... But only so long as they contain nothing more then music and sprites.
if (netgame && !(server || adminplayer == consoleplayer))
@@ -2958,7 +3039,7 @@ static void Command_Addfile(void)
}
// Add file on your client directly if it is trivial, or you aren't in a netgame.
- if (!(netgame || multiplayer) || W_VerifyNMUSlumps(fn))
+ if (!(netgame || multiplayer) || musiconly)
{
P_AddWadFile(fn, NULL);
return;
@@ -2978,9 +3059,7 @@ static void Command_Addfile(void)
#else
FILE *fhandle;
- fhandle = fopen(fn, "rb");
-
- if (fhandle)
+ if ((fhandle = W_OpenWadFile(&fn, true)) != NULL)
{
tic_t t = I_GetTime();
CONS_Debug(DBG_SETUP, "Making MD5 for %s\n",fn);
@@ -2988,11 +3067,8 @@ static void Command_Addfile(void)
CONS_Debug(DBG_SETUP, "MD5 calc for %s took %f second\n", fn, (float)(I_GetTime() - t)/TICRATE);
fclose(fhandle);
}
- else
- {
- CONS_Printf(M_GetText("File %s not found.\n"), fn);
+ else // file not found
return;
- }
#endif
WRITEMEM(buf_p, md5sum, 16);
}
@@ -3045,13 +3121,19 @@ static void Got_RequestAddfilecmd(UINT8 **cp, INT32 playernum)
filestatus_t ncs = FS_NOTFOUND;
UINT8 md5sum[16];
boolean kick = false;
+ boolean toomany = false;
INT32 i;
+ size_t packetsize = 0;
+ serverinfo_pak *dummycheck = NULL;
+
+ // Shut the compiler up.
+ (void)dummycheck;
READSTRINGN(*cp, filename, 240);
READMEM(*cp, md5sum, 16);
// Only the server processes this message.
- if (!server)
+ if (client)
return;
// Disallow non-printing characters and semicolons.
@@ -3071,13 +3153,25 @@ static void Got_RequestAddfilecmd(UINT8 **cp, INT32 playernum)
return;
}
- ncs = findfile(filename,md5sum,true);
+ // See W_LoadWadFile in w_wad.c
+ for (i = 0; i < numwadfiles; i++)
+ packetsize += nameonlylength(wadfiles[i]->filename) + 22;
- if (ncs != FS_FOUND)
+ packetsize += nameonlylength(filename) + 22;
+
+ if ((numwadfiles >= MAX_WADFILES)
+ || (packetsize > sizeof(dummycheck->fileneeded)))
+ toomany = true;
+ else
+ ncs = findfile(filename,md5sum,true);
+
+ if (ncs != FS_FOUND || toomany)
{
char message[256];
- if (ncs == FS_NOTFOUND)
+ if (toomany)
+ sprintf(message, M_GetText("Too many files loaded to add %s\n"), filename);
+ else if (ncs == FS_NOTFOUND)
sprintf(message, M_GetText("The server doesn't have %s\n"), filename);
else if (ncs == FS_MD5SUMBAD)
sprintf(message, M_GetText("Checksum mismatch on %s\n"), filename);
@@ -3147,10 +3241,15 @@ static void Got_Addfilecmd(UINT8 **cp, INT32 playernum)
ncs = findfile(filename,md5sum,true);
- if (ncs != FS_FOUND)
+ if (ncs != FS_FOUND || !P_AddWadFile(filename, NULL))
{
Command_ExitGame_f();
- if (ncs == FS_NOTFOUND)
+ if (ncs == FS_FOUND)
+ {
+ CONS_Printf(M_GetText("The server tried to add %s,\nbut you have too many files added.\nRestart the game to clear loaded files\nand play on this server."), filename);
+ M_StartMessage(va("The server added a file \n(%s)\nbut you have too many files added.\nRestart the game to clear loaded files.\n\nPress ESC\n",filename), NULL, MM_NOTHING);
+ }
+ else if (ncs == FS_NOTFOUND)
{
CONS_Printf(M_GetText("The server tried to add %s,\nbut you don't have this file.\nYou need to find it in order\nto play on this server."), filename);
M_StartMessage(va("The server added a file \n(%s)\nthat you do not have.\n\nPress ESC\n",filename), NULL, MM_NOTHING);
@@ -3168,7 +3267,6 @@ static void Got_Addfilecmd(UINT8 **cp, INT32 playernum)
return;
}
- P_AddWadFile(filename, NULL);
G_SetGameModified(true);
}
@@ -3318,6 +3416,11 @@ static void NetTimeout_OnChange(void)
connectiontimeout = (tic_t)cv_nettimeout.value;
}
+static void JoinTimeout_OnChange(void)
+{
+ jointimeout = (tic_t)cv_jointimeout.value;
+}
+
UINT32 timelimitintics = 0;
/** Deals with a timelimit change by printing the change to the console.
@@ -3960,7 +4063,7 @@ static void Command_Archivetest_f(void)
}
// assign mobjnum
- i = 0;
+ i = 1;
for (th = thinkercap.next; th != &thinkercap; th = th->next)
if (th->function.acp1 == (actionf_p1)P_MobjThinker)
((mobj_t *)th)->mobjnum = i++;
@@ -4002,13 +4105,6 @@ static void Command_Archivetest_f(void)
*/
static void ForceSkin_OnChange(void)
{
- if ((server || adminplayer == consoleplayer) && ((cv_forceskin.value == -1 && stricmp(cv_forceskin.string, "None")) || !(R_SkinUnlock(cv_forceskin.value))))
- {
- CONS_Printf("Please provide a valid skin name (\"None\" disables).\n");
- CV_SetValue(&cv_forceskin, -1);
- return;
- }
-
// NOT in SP, silly!
if (!(netgame || multiplayer))
return;
@@ -4017,7 +4113,7 @@ static void ForceSkin_OnChange(void)
CONS_Printf("The server has lifted the forced skin restrictions.\n");
else
{
- CONS_Printf("The server is restricting all players to skin \"%s\".\n",skins[cv_forceskin.value].realname);
+ CONS_Printf("The server is restricting all players to skin \"%s\".\n",skins[cv_forceskin.value].name);
ForceAllSkins(cv_forceskin.value);
}
}
@@ -4056,7 +4152,7 @@ static void Skin_OnChange(void)
return; // do whatever you want
if (!(cv_debug || devparm) && !(multiplayer || netgame) // In single player.
- && (gamestate == GS_LEVEL || gamestate == GS_INTERMISSION || gamestate == GS_CONTINUING))
+ && (gamestate != GS_WAITINGPLAYERS)) // allows command line -warp x +skin y
{
CV_StealthSet(&cv_skin, skins[players[consoleplayer].skin].name);
return;
@@ -4099,8 +4195,7 @@ static void Color_OnChange(void)
if (!Playing())
return; // do whatever you want
- if (!(cv_debug || devparm) && !(multiplayer || netgame) // In single player.
- && (gamestate == GS_LEVEL || gamestate == GS_INTERMISSION || gamestate == GS_CONTINUING))
+ if (!(cv_debug || devparm) && !(multiplayer || netgame)) // In single player.
{
CV_StealthSet(&cv_skin, skins[players[consoleplayer].skin].name);
return;
diff --git a/src/d_netcmd.h b/src/d_netcmd.h
index c090699f1..80481c6a5 100644
--- a/src/d_netcmd.h
+++ b/src/d_netcmd.h
@@ -20,6 +20,12 @@
// console vars
extern consvar_t cv_playername;
extern consvar_t cv_playercolor;
+extern consvar_t cv_skin;
+// secondary splitscreen player
+extern consvar_t cv_playername2;
+extern consvar_t cv_playercolor2;
+extern consvar_t cv_skin2;
+
#ifdef SEENAMES
extern consvar_t cv_seenames, cv_allowseenames;
#endif
@@ -32,7 +38,6 @@ extern consvar_t cv_joyport2;
#endif
extern consvar_t cv_joyscale;
extern consvar_t cv_joyscale2;
-extern consvar_t cv_controlperkey;
// splitscreen with second mouse
extern consvar_t cv_mouse2port;
@@ -40,25 +45,12 @@ extern consvar_t cv_usemouse2;
#if (defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON)
extern consvar_t cv_mouse2opt;
#endif
-extern consvar_t cv_invertmouse2;
-extern consvar_t cv_alwaysfreelook2;
-extern consvar_t cv_mousemove2;
-extern consvar_t cv_mousesens2;
-extern consvar_t cv_mouseysens2;
// normally in p_mobj but the .h is not read
extern consvar_t cv_itemrespawntime;
extern consvar_t cv_itemrespawn;
extern consvar_t cv_flagtime;
-extern consvar_t cv_suddendeath;
-
-extern consvar_t cv_skin;
-
-// secondary splitscreen player
-extern consvar_t cv_playername2;
-extern consvar_t cv_playercolor2;
-extern consvar_t cv_skin2;
extern consvar_t cv_touchtag;
extern consvar_t cv_hidetime;
@@ -77,9 +69,6 @@ extern consvar_t cv_autobalance;
extern consvar_t cv_teamscramble;
extern consvar_t cv_scrambleonchange;
-extern consvar_t cv_useranalog, cv_useranalog2;
-extern consvar_t cv_analog, cv_analog2;
-
extern consvar_t cv_netstat;
#ifdef WALLSPLATS
extern consvar_t cv_splats;
@@ -101,8 +90,6 @@ extern consvar_t cv_recycler;
extern consvar_t cv_itemfinder;
extern consvar_t cv_inttime, cv_advancemap, cv_playersforexit;
-extern consvar_t cv_soniccd;
-extern consvar_t cv_match_scoring;
extern consvar_t cv_overtime;
extern consvar_t cv_startinglives;
@@ -121,17 +108,7 @@ extern consvar_t cv_maxping;
extern consvar_t cv_skipmapcheck;
-extern consvar_t cv_sleep, cv_screenshot_option, cv_screenshot_folder;
-
-extern consvar_t cv_moviemode;
-
-extern consvar_t cv_zlib_level, cv_zlib_memory, cv_zlib_strategy;
-
-extern consvar_t cv_zlib_window_bits, cv_zlib_levela, cv_zlib_memorya;
-
-extern consvar_t cv_zlib_strategya, cv_zlib_window_bitsa;
-
-extern consvar_t cv_apng_delay;
+extern consvar_t cv_sleep;
typedef enum
{
@@ -162,6 +139,8 @@ typedef enum
MAXNETXCMD
} netxcmd_t;
+extern const char *netxcmdnames[MAXNETXCMD - 1];
+
#if defined(_MSC_VER)
#pragma pack(1)
#endif
@@ -210,7 +189,6 @@ void Command_ExitGame_f(void);
void Command_Retry_f(void);
void D_GameTypeChanged(INT32 lastgametype); // not a real _OnChange function anymore
void D_MapChange(INT32 pmapnum, INT32 pgametype, boolean pultmode, boolean presetplayers, INT32 pdelay, boolean pskipprecutscene, boolean pfromlevelselect);
-void ObjectPlace_OnChange(void);
void ItemFinder_OnChange(void);
void D_SetPassword(const char *pw);
diff --git a/src/d_netfil.c b/src/d_netfil.c
index 85196217f..172624ad2 100644
--- a/src/d_netfil.c
+++ b/src/d_netfil.c
@@ -62,44 +62,49 @@
#include
-static void SendFile(INT32 node, const char *filename, UINT8 fileid);
+// Prototypes
+static boolean SV_SendFile(INT32 node, const char *filename, UINT8 fileid);
-// sender structure
+// Sender structure
typedef struct filetx_s
{
INT32 ram;
- char *filename; // name of the file or ptr of the data in ram
- UINT32 size;
+ union {
+ char *filename; // Name of the file
+ char *ram; // Pointer to the data in RAM
+ } id;
+ UINT32 size; // Size of the file
UINT8 fileid;
- INT32 node; // destination
- struct filetx_s *next; // a queue
+ INT32 node; // Destination
+ struct filetx_s *next; // Next file in the list
} filetx_t;
-// current transfers (one for each node)
+// Current transfers (one for each node)
typedef struct filetran_s
{
- filetx_t *txlist;
- UINT32 position;
- FILE *currentfile;
+ filetx_t *txlist; // Linked list of all files for the node
+ UINT32 position; // The current position in the file
+ FILE *currentfile; // The file currently being sent/received
} filetran_t;
static filetran_t transfer[MAXNETNODES];
-// read time of file: stat _stmtime
-// write time of file: utime
+// Read time of file: stat _stmtime
+// Write time of file: utime
-// receiver structure
-INT32 fileneedednum;
-fileneeded_t fileneeded[MAX_WADFILES];
+// Receiver structure
+INT32 fileneedednum; // Number of files needed to join the server
+fileneeded_t fileneeded[MAX_WADFILES]; // List of needed files
char downloaddir[256] = "DOWNLOAD";
#ifdef CLIENT_LOADINGSCREEN
// for cl loading screen
-INT32 lastfilenum = 0;
+INT32 lastfilenum = -1;
#endif
/** Fills a serverinfo packet with information about wad files loaded.
*
* \todo Give this function a better name since it is in global scope.
+ *
*/
UINT8 *PutFileNeeded(void)
{
@@ -111,19 +116,19 @@ UINT8 *PutFileNeeded(void)
for (i = 0; i < numwadfiles; i++)
{
- // if it has only music/sound lumps, mark it as unimportant
+ // If it has only music/sound lumps, mark it as unimportant
if (W_VerifyNMUSlumps(wadfiles[i]->filename))
filestatus = 0;
else
- filestatus = 1; // important
+ filestatus = 1; // Important
// Store in the upper four bits
if (!cv_downloading.value)
- filestatus += (2 << 4); // won't send
+ filestatus += (2 << 4); // Won't send
else if ((wadfiles[i]->filesize > (UINT32)cv_maxsend.value * 1024))
- filestatus += (0 << 4); // won't send
+ filestatus += (0 << 4); // Won't send
else
- filestatus += (1 << 4); // will send if requested
+ filestatus += (1 << 4); // Will send if requested
bytesused += (nameonlylength(wadfilename) + 22);
@@ -144,7 +149,12 @@ UINT8 *PutFileNeeded(void)
return p;
}
-// parse the serverinfo packet and fill fileneeded table on client
+/** Parses the serverinfo packet and fills the fileneeded table on client
+ *
+ * \param fileneedednum_parm The number of files needed to join the server
+ * \param fileneededstr The memory block containing the list of needed files
+ *
+ */
void D_ParseFileneeded(INT32 fileneedednum_parm, UINT8 *fileneededstr)
{
INT32 i;
@@ -155,14 +165,14 @@ void D_ParseFileneeded(INT32 fileneedednum_parm, UINT8 *fileneededstr)
p = (UINT8 *)fileneededstr;
for (i = 0; i < fileneedednum; i++)
{
- fileneeded[i].status = FS_NOTFOUND;
- filestatus = READUINT8(p);
+ fileneeded[i].status = FS_NOTFOUND; // We haven't even started looking for the file yet
+ filestatus = READUINT8(p); // The first byte is the file status
fileneeded[i].important = (UINT8)(filestatus & 3);
fileneeded[i].willsend = (UINT8)(filestatus >> 4);
- fileneeded[i].totalsize = READUINT32(p);
- fileneeded[i].phandle = NULL;
- READSTRINGN(p, fileneeded[i].filename, MAX_WADPATH);
- READMEM(p, fileneeded[i].md5sum, 16);
+ fileneeded[i].totalsize = READUINT32(p); // The four next bytes are the file size
+ fileneeded[i].file = NULL; // The file isn't open yet
+ READSTRINGN(p, fileneeded[i].filename, MAX_WADPATH); // The next bytes are the file name
+ READMEM(p, fileneeded[i].md5sum, 16); // The last 16 bytes are the file checksum
}
}
@@ -171,13 +181,16 @@ void CL_PrepareDownloadSaveGame(const char *tmpsave)
fileneedednum = 1;
fileneeded[0].status = FS_REQUESTED;
fileneeded[0].totalsize = UINT32_MAX;
- fileneeded[0].phandle = NULL;
+ fileneeded[0].file = NULL;
memset(fileneeded[0].md5sum, 0, 16);
strcpy(fileneeded[0].filename, tmpsave);
}
/** Checks the server to see if we CAN download all the files,
* before starting to create them and requesting.
+ *
+ * \return True if we can download all the files
+ *
*/
boolean CL_CheckDownloadable(void)
{
@@ -239,8 +252,12 @@ boolean CL_CheckDownloadable(void)
return false;
}
-/** Send requests for files in the ::fileneeded table with a status of
+/** Sends requests for files in the ::fileneeded table with a status of
* ::FS_NOTFOUND.
+ *
+ * \return True if the packet was successfully sent
+ * \note Sends a PT_REQUESTFILE packet
+ *
*/
boolean CL_SendRequestFile(void)
{
@@ -287,7 +304,8 @@ boolean CL_SendRequestFile(void)
}
// get request filepak and put it on the send queue
-void Got_RequestFilePak(INT32 node)
+// returns false if a requested file was not found or cannot be sent
+boolean Got_RequestFilePak(INT32 node)
{
char wad[MAX_WADPATH+1];
UINT8 *p = netbuffer->u.textcmd;
@@ -298,16 +316,33 @@ void Got_RequestFilePak(INT32 node)
if (id == 0xFF)
break;
READSTRINGN(p, wad, MAX_WADPATH);
- SendFile(node, wad, id);
+ if (!SV_SendFile(node, wad, id))
+ {
+ SV_AbortSendFiles(node);
+ return false; // don't read the rest of the files
+ }
}
+ return true; // no problems with any files
}
-// client check if the fileneeded aren't already loaded or on the disk
+/** Checks if the files needed aren't already loaded or on the disk
+ *
+ * \return 0 if some files are missing
+ * 1 if all files exist
+ * 2 if some already loaded files are not requested or are in a different order
+ *
+ */
INT32 CL_CheckFiles(void)
{
INT32 i, j;
char wadfilename[MAX_WADPATH];
INT32 ret = 1;
+ size_t packetsize = 0;
+ size_t filestoget = 0;
+ serverinfo_pak *dummycheck = NULL;
+
+ // Shut the compiler up.
+ (void)dummycheck;
// if (M_CheckParm("-nofiles"))
// return 1;
@@ -333,7 +368,7 @@ INT32 CL_CheckFiles(void)
}
if (j < numwadfiles && W_VerifyNMUSlumps(wadfiles[j]->filename))
{
- // unimportant on our side. still don't care.
+ // Unimportant on our side. still don't care.
++j;
continue;
}
@@ -343,11 +378,11 @@ INT32 CL_CheckFiles(void)
if (i >= fileneedednum || j >= numwadfiles)
return 2;
- // for the sake of speed, only bother with a md5 check
+ // For the sake of speed, only bother with a md5 check
if (memcmp(wadfiles[j]->md5sum, fileneeded[i].md5sum, 16))
return 2;
- // it's accounted for! let's keep going.
+ // It's accounted for! let's keep going.
CONS_Debug(DBG_NETPLAY, "'%s' accounted for\n", fileneeded[i].filename);
fileneeded[i].status = FS_OPEN;
++i;
@@ -356,11 +391,15 @@ INT32 CL_CheckFiles(void)
return 1;
}
+ // See W_LoadWadFile in w_wad.c
+ for (i = 0; i < numwadfiles; i++)
+ packetsize += nameonlylength(wadfiles[i]->filename) + 22;
+
for (i = 1; i < fileneedednum; i++)
{
CONS_Debug(DBG_NETPLAY, "searching for '%s' ", fileneeded[i].filename);
- // check in allready loaded files
+ // Check in already loaded files
for (j = 1; wadfiles[j]; j++)
{
nameonly(strcpy(wadfilename, wadfiles[j]->filename));
@@ -375,6 +414,14 @@ INT32 CL_CheckFiles(void)
if (fileneeded[i].status != FS_NOTFOUND || !fileneeded[i].important)
continue;
+ packetsize += nameonlylength(fileneeded[i].filename) + 22;
+
+ if ((numwadfiles+filestoget >= MAX_WADFILES)
+ || (packetsize > sizeof(dummycheck->fileneeded)))
+ return 3;
+
+ filestoget++;
+
fileneeded[i].status = findfile(fileneeded[i].filename, fileneeded[i].md5sum, true);
CONS_Debug(DBG_NETPLAY, "found %d\n", fileneeded[i].status);
if (fileneeded[i].status != FS_FOUND)
@@ -383,7 +430,7 @@ INT32 CL_CheckFiles(void)
return ret;
}
-// load it now
+// Load it now
void CL_LoadServerFiles(void)
{
INT32 i;
@@ -394,7 +441,7 @@ void CL_LoadServerFiles(void)
for (i = 1; i < fileneedednum; i++)
{
if (fileneeded[i].status == FS_OPEN)
- continue; // already loaded
+ continue; // Already loaded
else if (fileneeded[i].status == FS_FOUND)
{
P_AddWadFile(fileneeded[i].filename, NULL);
@@ -423,172 +470,270 @@ void CL_LoadServerFiles(void)
DEBFILE(va("File %s found but with different md5sum\n", fileneeded[i].filename));
}
else if (fileneeded[i].important)
- I_Error("Try to load file %s with status of %d\n", fileneeded[i].filename,
- fileneeded[i].status);
+ {
+ const char *s;
+ switch(fileneeded[i].status)
+ {
+ case FS_NOTFOUND:
+ s = "FS_NOTFOUND";
+ break;
+ case FS_REQUESTED:
+ s = "FS_REQUESTED";
+ break;
+ case FS_DOWNLOADING:
+ s = "FS_DOWNLOADING";
+ break;
+ default:
+ s = "unknown";
+ break;
+ }
+ I_Error("Try to load file \"%s\" with status of %d (%s)\n", fileneeded[i].filename,
+ fileneeded[i].status, s);
+ }
}
}
-// little optimization to test if there is a file in the queue
-static INT32 filetosend = 0;
+// Number of files to send
+// Little optimization to quickly test if there is a file in the queue
+static INT32 filestosend = 0;
-static void SendFile(INT32 node, const char *filename, UINT8 fileid)
+/** Adds a file to the file list for a node
+ *
+ * \param node The node to send the file to
+ * \param filename The file to send
+ * \param fileid ???
+ * \sa SV_SendRam
+ *
+ */
+static boolean SV_SendFile(INT32 node, const char *filename, UINT8 fileid)
{
- filetx_t **q;
- filetx_t *p;
+ filetx_t **q; // A pointer to the "next" field of the last file in the list
+ filetx_t *p; // The new file request
INT32 i;
char wadfilename[MAX_WADPATH];
+ if (cv_noticedownload.value)
+ CONS_Printf("Sending file \"%s\" to node %d (%s)\n", filename, node, I_GetNodeAddress(node));
+
+ // Find the last file in the list and set a pointer to its "next" field
q = &transfer[node].txlist;
while (*q)
q = &((*q)->next);
+
+ // Allocate a file request and append it to the file list
p = *q = (filetx_t *)malloc(sizeof (filetx_t));
- if (p)
- memset(p, 0, sizeof (filetx_t));
- else
- I_Error("SendFile: No more ram\n");
- p->filename = (char *)malloc(MAX_WADPATH);
- if (!p->filename)
- I_Error("SendFile: No more ram\n");
+ if (!p)
+ I_Error("SV_SendFile: No more memory\n");
- // a minimum of security, can get only file in srb2 direcory
- strlcpy(p->filename, filename, MAX_WADPATH);
- nameonly(p->filename);
+ // Initialise with zeros
+ memset(p, 0, sizeof (filetx_t));
- // check first in wads loaded the majority of case
+ // Allocate the file name
+ p->id.filename = (char *)malloc(MAX_WADPATH);
+ if (!p->id.filename)
+ I_Error("SV_SendFile: No more memory\n");
+
+ // Set the file name and get rid of the path
+ strlcpy(p->id.filename, filename, MAX_WADPATH);
+ nameonly(p->id.filename);
+
+ // Look for the requested file through all loaded files
for (i = 0; wadfiles[i]; i++)
{
strlcpy(wadfilename, wadfiles[i]->filename, MAX_WADPATH);
nameonly(wadfilename);
- if (!stricmp(wadfilename, p->filename))
+ if (!stricmp(wadfilename, p->id.filename))
{
- // copy filename with full path
- strlcpy(p->filename, wadfiles[i]->filename, MAX_WADPATH);
+ // Copy file name with full path
+ strlcpy(p->id.filename, wadfiles[i]->filename, MAX_WADPATH);
break;
}
}
+ // Handle non-loaded file requests
if (!wadfiles[i])
{
DEBFILE(va("%s not found in wadfiles\n", filename));
- // this formerly checked if (!findfile(p->filename, NULL, true))
+ // This formerly checked if (!findfile(p->id.filename, NULL, true))
- // not found
- // don't inform client (probably hacker)
+ // Not found
+ // Don't inform client (probably someone who thought they could leak 2.2 ACZ)
DEBFILE(va("Client %d request %s: not found\n", node, filename));
- free(p->filename);
+ free(p->id.filename);
free(p);
*q = NULL;
- return;
+ return false; // cancel the rest of the requests
}
+ // Handle huge file requests (i.e. bigger than cv_maxsend.value KB)
if (wadfiles[i]->filesize > (UINT32)cv_maxsend.value * 1024)
{
- // too big
- // don't inform client (client sucks, man)
+ // Too big
+ // Don't inform client (client sucks, man)
DEBFILE(va("Client %d request %s: file too big, not sending\n", node, filename));
- free(p->filename);
+ free(p->id.filename);
free(p);
*q = NULL;
- return;
+ return false; // cancel the rest of the requests
}
DEBFILE(va("Sending file %s (id=%d) to %d\n", filename, fileid, node));
- p->ram = SF_FILE;
+ p->ram = SF_FILE; // It's a file, we need to close it and free its name once we're done sending it
p->fileid = fileid;
- p->next = NULL; // end of list
- filetosend++;
+ p->next = NULL; // End of list
+ filestosend++;
+ return true;
}
-void SendRam(INT32 node, void *data, size_t size, freemethod_t freemethod, UINT8 fileid)
+/** Adds a memory block to the file list for a node
+ *
+ * \param node The node to send the memory block to
+ * \param data The memory block to send
+ * \param size The size of the block in bytes
+ * \param freemethod How to free the block after it has been sent
+ * \param fileid ???
+ * \sa SV_SendFile
+ *
+ */
+void SV_SendRam(INT32 node, void *data, size_t size, freemethod_t freemethod, UINT8 fileid)
{
- filetx_t **q;
- filetx_t *p;
+ filetx_t **q; // A pointer to the "next" field of the last file in the list
+ filetx_t *p; // The new file request
+ // Find the last file in the list and set a pointer to its "next" field
q = &transfer[node].txlist;
while (*q)
q = &((*q)->next);
+
+ // Allocate a file request and append it to the file list
p = *q = (filetx_t *)malloc(sizeof (filetx_t));
- if (p)
- memset(p, 0, sizeof (filetx_t));
- else
- I_Error("SendRam: No more ram\n");
- p->ram = freemethod;
- p->filename = data;
+ if (!p)
+ I_Error("SV_SendRam: No more memory\n");
+
+ // Initialise with zeros
+ memset(p, 0, sizeof (filetx_t));
+
+ p->ram = freemethod; // Remember how to free the memory block for when we're done sending it
+ p->id.ram = data;
p->size = (UINT32)size;
p->fileid = fileid;
- p->next = NULL; // end of list
+ p->next = NULL; // End of list
- DEBFILE(va("Sending ram %p(size:%u) to %d (id=%u)\n",p->filename,p->size,node,fileid));
+ DEBFILE(va("Sending ram %p(size:%u) to %d (id=%u)\n",p->id.ram,p->size,node,fileid));
- filetosend++;
+ filestosend++;
}
-static void EndSend(INT32 node)
+/** Stops sending a file for a node, and removes the file request from the list,
+ * either because the file has been fully sent or because the node was disconnected
+ *
+ * \param node The destination
+ *
+ */
+static void SV_EndFileSend(INT32 node)
{
filetx_t *p = transfer[node].txlist;
+
+ // Free the file request according to the freemethod parameter used with SV_SendFile/Ram
switch (p->ram)
{
- case SF_FILE:
+ case SF_FILE: // It's a file, close it and free its filename
+ if (cv_noticedownload.value)
+ CONS_Printf("Ending file transfer for node %d\n", node);
if (transfer[node].currentfile)
fclose(transfer[node].currentfile);
- free(p->filename);
+ free(p->id.filename);
break;
- case SF_Z_RAM:
- Z_Free(p->filename);
+ case SF_Z_RAM: // It's a memory block allocated with Z_Alloc or the likes, use Z_Free
+ Z_Free(p->id.ram);
break;
- case SF_RAM:
- free(p->filename);
- case SF_NOFREERAM:
+ case SF_RAM: // It's a memory block allocated with malloc, use free
+ free(p->id.ram);
+ case SF_NOFREERAM: // Nothing to free
break;
}
+
+ // Remove the file request from the list
transfer[node].txlist = p->next;
- transfer[node].currentfile = NULL;
free(p);
- filetosend--;
+
+ // Indicate that the transmission is over
+ transfer[node].currentfile = NULL;
+
+ filestosend--;
}
#define PACKETPERTIC net_bandwidth/(TICRATE*software_MAXPACKETLENGTH)
-void FiletxTicker(void)
+/** Handles file transmission
+ *
+ * \todo Use an acknowledging method more adapted to file transmission
+ * The current download speed suffers from lack of ack packets,
+ * especially when the one downloading has high latency
+ *
+ */
+void SV_FileSendTicker(void)
{
static INT32 currentnode = 0;
filetx_pak *p;
size_t size;
filetx_t *f;
- INT32 packetsent = PACKETPERTIC, ram, i;
+ INT32 packetsent, ram, i, j;
+ INT32 maxpacketsent;
- if (!filetosend)
+ if (!filestosend) // No file to send
return;
- if (!packetsent)
- packetsent++;
- // (((sendbytes-nowsentbyte)*TICRATE)/(I_GetTime()-starttime)<(UINT32)net_bandwidth)
- while (packetsent-- && filetosend != 0)
+
+ if (cv_downloadspeed.value) // New (and experimental) behavior
{
- for (i = currentnode, ram = 0; ram < MAXNETNODES;
- i = (i+1) % MAXNETNODES, ram++)
+ packetsent = cv_downloadspeed.value;
+ // Don't send more packets than we have free acks
+#ifndef NONET
+ maxpacketsent = Net_GetFreeAcks(false) - 5; // Let 5 extra acks just in case
+#else
+ maxpacketsent = 1;
+#endif
+ if (packetsent > maxpacketsent && maxpacketsent > 0) // Send at least one packet
+ packetsent = maxpacketsent;
+ }
+ else // Old behavior
+ {
+ packetsent = PACKETPERTIC;
+ if (!packetsent)
+ packetsent = 1;
+ }
+
+ netbuffer->packettype = PT_FILEFRAGMENT;
+
+ // (((sendbytes-nowsentbyte)*TICRATE)/(I_GetTime()-starttime)<(UINT32)net_bandwidth)
+ while (packetsent-- && filestosend != 0)
+ {
+ for (i = currentnode, j = 0; j < MAXNETNODES;
+ i = (i+1) % MAXNETNODES, j++)
{
if (transfer[i].txlist)
goto found;
}
// no transfer to do
- I_Error("filetosend=%d but no filetosend found\n", filetosend);
+ I_Error("filestosend=%d but no file to send found\n", filestosend);
found:
currentnode = (i+1) % MAXNETNODES;
f = transfer[i].txlist;
ram = f->ram;
- if (!transfer[i].currentfile) // file not already open
+ // Open the file if it isn't open yet, or
+ if (!transfer[i].currentfile)
{
- if (!ram)
+ if (!ram) // Sending a file
{
long filesize;
transfer[i].currentfile =
- fopen(f->filename, "rb");
+ fopen(f->id.filename, "rb");
if (!transfer[i].currentfile)
I_Error("File %s does not exist",
- f->filename);
+ f->id.filename);
fseek(transfer[i].currentfile, 0, SEEK_END);
filesize = ftell(transfer[i].currentfile);
@@ -596,45 +741,47 @@ void FiletxTicker(void)
// Nobody wants to transfer a file bigger
// than 4GB!
if (filesize >= LONG_MAX)
- I_Error("filesize of %s is too large", f->filename);
- if (-1 == filesize)
- I_Error("Error getting filesize of %s", f->filename);
+ I_Error("filesize of %s is too large", f->id.filename);
+ if (filesize == -1)
+ I_Error("Error getting filesize of %s", f->id.filename);
f->size = (UINT32)filesize;
fseek(transfer[i].currentfile, 0, SEEK_SET);
}
- else
- transfer[i].currentfile = (FILE *)1;
+ else // Sending RAM
+ transfer[i].currentfile = (FILE *)1; // Set currentfile to a non-null value to indicate that it is open
transfer[i].position = 0;
}
+ // Build a packet containing a file fragment
p = &netbuffer->u.filetxpak;
size = software_MAXPACKETLENGTH - (FILETXHEADER + BASEPACKETSIZE);
if (f->size-transfer[i].position < size)
size = f->size-transfer[i].position;
if (ram)
- M_Memcpy(p->data, &f->filename[transfer[i].position], size);
+ M_Memcpy(p->data, &f->id.ram[transfer[i].position], size);
else if (fread(p->data, 1, size, transfer[i].currentfile) != size)
- I_Error("FiletxTicker: can't read %s byte on %s at %d because %s", sizeu1(size), f->filename, transfer[i].position, strerror(ferror(transfer[i].currentfile)));
+ I_Error("SV_FileSendTicker: can't read %s byte on %s at %d because %s", sizeu1(size), f->id.filename, transfer[i].position, strerror(ferror(transfer[i].currentfile)));
p->position = LONG(transfer[i].position);
- // put flag so receiver know the totalsize
+ // Put flag so receiver knows the total size
if (transfer[i].position + size == f->size)
p->position |= LONG(0x80000000);
p->fileid = f->fileid;
p->size = SHORT((UINT16)size);
- netbuffer->packettype = PT_FILEFRAGMENT;
- if (!HSendPacket(i, true, 0, FILETXHEADER + size)) // reliable SEND
- { // not sent for some odd reason, retry at next call
- if (!ram)
- fseek(transfer[i].currentfile,transfer[i].position,SEEK_SET);
- // exit the while (can't send this one so why should i send the next?)
- break;
+
+ // Send the packet
+ if (HSendPacket(i, true, 0, FILETXHEADER + size)) // Reliable SEND
+ { // Success
+ transfer[i].position = (UINT32)(transfer[i].position + size);
+ if (transfer[i].position == f->size) // Finish?
+ SV_EndFileSend(i);
}
- else // success
- {
- transfer[i].position = (UINT32)(size+transfer[i].position);
- if (transfer[i].position == f->size) // finish ?
- EndSend(i);
+ else
+ { // Not sent for some odd reason, retry at next call
+ if (!ram)
+ fseek(transfer[i].currentfile,transfer[i].position, SEEK_SET);
+ // Exit the while (can't send this one so why should i send the next?)
+ break;
}
}
}
@@ -642,55 +789,90 @@ void FiletxTicker(void)
void Got_Filetxpak(void)
{
INT32 filenum = netbuffer->u.filetxpak.fileid;
+ fileneeded_t *file = &fileneeded[filenum];
+ char *filename = file->filename;
static INT32 filetime = 0;
+ if (!(strcmp(filename, "srb2.srb")
+ && strcmp(filename, "srb2.wad")
+ && strcmp(filename, "zones.dta")
+ && strcmp(filename, "player.dta")
+ && strcmp(filename, "rings.dta")
+ && strcmp(filename, "patch.dta")
+ && strcmp(filename, "music.dta")
+ ))
+ I_Error("Tried to download \"%s\"", filename);
+
if (filenum >= fileneedednum)
{
- DEBFILE(va("fileframent not needed %d>%d\n",filenum, fileneedednum));
+ DEBFILE(va("fileframent not needed %d>%d\n", filenum, fileneedednum));
+ //I_Error("Received an unneeded file fragment (file id received: %d, file id needed: %d)\n", filenum, fileneedednum);
return;
}
- if (fileneeded[filenum].status == FS_REQUESTED)
+ if (file->status == FS_REQUESTED)
{
- if (fileneeded[filenum].phandle) I_Error("Got_Filetxpak: allready open file\n");
- fileneeded[filenum].phandle = fopen(fileneeded[filenum].filename, "wb");
- if (!fileneeded[filenum].phandle) I_Error("Can't create file %s: %s",fileneeded[filenum].filename, strerror(errno));
- CONS_Printf("\r%s...\n",fileneeded[filenum].filename);
- fileneeded[filenum].currentsize = 0;
- fileneeded[filenum].status = FS_DOWNLOADING;
+ if (file->file)
+ I_Error("Got_Filetxpak: already open file\n");
+ file->file = fopen(filename, "wb");
+ if (!file->file)
+ I_Error("Can't create file %s: %s", filename, strerror(errno));
+ CONS_Printf("\r%s...\n",filename);
+ file->currentsize = 0;
+ file->status = FS_DOWNLOADING;
}
- if (fileneeded[filenum].status == FS_DOWNLOADING)
+ if (file->status == FS_DOWNLOADING)
{
UINT32 pos = LONG(netbuffer->u.filetxpak.position);
UINT16 size = SHORT(netbuffer->u.filetxpak.size);
- // use a special tric to know when file is finished (not allways used)
- // WARNING: filepak can arrive out of order so don't stop now !
+ // Use a special trick to know when the file is complete (not always used)
+ // WARNING: file fragments can arrive out of order so don't stop yet!
if (pos & 0x80000000)
{
pos &= ~0x80000000;
- fileneeded[filenum].totalsize = pos + size;
+ file->totalsize = pos + size;
}
- // we can receive packet in the wrong order, anyway all os support gaped file
- fseek(fileneeded[filenum].phandle,pos,SEEK_SET);
- if (fwrite(netbuffer->u.filetxpak.data,size,1,fileneeded[filenum].phandle)!=1)
- I_Error("Can't write to %s: %s\n",fileneeded[filenum].filename, strerror(ferror(fileneeded[filenum].phandle)));
- fileneeded[filenum].currentsize += size;
+ // We can receive packet in the wrong order, anyway all os support gaped file
+ fseek(file->file, pos, SEEK_SET);
+ if (fwrite(netbuffer->u.filetxpak.data,size,1,file->file) != 1)
+ I_Error("Can't write to %s: %s\n",filename, strerror(ferror(file->file)));
+ file->currentsize += size;
- // finished?
- if (fileneeded[filenum].currentsize == fileneeded[filenum].totalsize)
+ // Finished?
+ if (file->currentsize == file->totalsize)
{
- fclose(fileneeded[filenum].phandle);
- fileneeded[filenum].phandle = NULL;
- fileneeded[filenum].status = FS_FOUND;
+ fclose(file->file);
+ file->file = NULL;
+ file->status = FS_FOUND;
CONS_Printf(M_GetText("Downloading %s...(done)\n"),
- fileneeded[filenum].filename);
+ filename);
}
}
else
- I_Error("Received a file not requested\n");
- // send ack back quickly
-
+ {
+ const char *s;
+ switch(file->status)
+ {
+ case FS_NOTFOUND:
+ s = "FS_NOTFOUND";
+ break;
+ case FS_FOUND:
+ s = "FS_FOUND";
+ break;
+ case FS_OPEN:
+ s = "FS_OPEN";
+ break;
+ case FS_MD5SUMBAD:
+ s = "FS_MD5SUMBAD";
+ break;
+ default:
+ s = "unknown";
+ break;
+ }
+ I_Error("Received a file not requested (file id: %d, file status: %s)\n", filenum, s);
+ }
+ // Send ack back quickly
if (++filetime == 3)
{
Net_SendAcks(servernode);
@@ -702,33 +884,50 @@ void Got_Filetxpak(void)
#endif
}
-void AbortSendFiles(INT32 node)
+/** \brief Checks if a node is downloading a file
+ *
+ * \param node The node to check for
+ * \return True if the node is downloading a file
+ *
+ */
+boolean SV_SendingFile(INT32 node)
+{
+ return transfer[node].txlist != NULL;
+}
+
+/** Cancels all file requests for a node
+ *
+ * \param node The destination
+ * \sa SV_EndFileSend
+ *
+ */
+void SV_AbortSendFiles(INT32 node)
{
while (transfer[node].txlist)
- EndSend(node);
+ SV_EndFileSend(node);
}
void CloseNetFile(void)
{
INT32 i;
- // is sending?
+ // Is sending?
for (i = 0; i < MAXNETNODES; i++)
- AbortSendFiles(i);
+ SV_AbortSendFiles(i);
- // receiving a file?
+ // Receiving a file?
for (i = 0; i < MAX_WADFILES; i++)
- if (fileneeded[i].status == FS_DOWNLOADING && fileneeded[i].phandle)
+ if (fileneeded[i].status == FS_DOWNLOADING && fileneeded[i].file)
{
- fclose(fileneeded[i].phandle);
- // file is not complete delete it
+ fclose(fileneeded[i].file);
+ // File is not complete delete it
remove(fileneeded[i].filename);
}
- // remove FILEFRAGMENT from acknledge list
+ // Remove PT_FILEFRAGMENT from acknowledge list
Net_AbortPacketType(PT_FILEFRAGMENT);
}
-// functions cut and pasted from doomatic :)
+// Functions cut and pasted from Doomatic :)
void nameonly(char *s)
{
diff --git a/src/d_netfil.h b/src/d_netfil.h
index a68119f10..b9b7b2f2e 100644
--- a/src/d_netfil.h
+++ b/src/d_netfil.h
@@ -29,21 +29,21 @@ typedef enum
FS_FOUND,
FS_REQUESTED,
FS_DOWNLOADING,
- FS_OPEN, // is opened and used in w_wad
+ FS_OPEN, // Is opened and used in w_wad
FS_MD5SUMBAD
} filestatus_t;
typedef struct
{
UINT8 important;
- UINT8 willsend; // is the server willing to send it?
+ UINT8 willsend; // Is the server willing to send it?
char filename[MAX_WADPATH];
UINT8 md5sum[16];
- // used only for download
- FILE *phandle;
+ // Used only for download
+ FILE *file;
UINT32 currentsize;
UINT32 totalsize;
- filestatus_t status; // the value returned by recsearch
+ filestatus_t status; // The value returned by recsearch
} fileneeded_t;
extern INT32 fileneedednum;
@@ -58,28 +58,25 @@ UINT8 *PutFileNeeded(void);
void D_ParseFileneeded(INT32 fileneedednum_parm, UINT8 *fileneededstr);
void CL_PrepareDownloadSaveGame(const char *tmpsave);
-// check file list in wadfiles return 0 when a file is not found
-// 1 if all file are found
-// 2 if you cannot connect (different wad version or
-// no enought space to download files)
INT32 CL_CheckFiles(void);
void CL_LoadServerFiles(void);
-void SendRam(INT32 node, void *data, size_t size, freemethod_t freemethod,
+void SV_SendRam(INT32 node, void *data, size_t size, freemethod_t freemethod,
UINT8 fileid);
-void FiletxTicker(void);
+void SV_FileSendTicker(void);
void Got_Filetxpak(void);
+boolean SV_SendingFile(INT32 node);
boolean CL_CheckDownloadable(void);
boolean CL_SendRequestFile(void);
-void Got_RequestFilePak(INT32 node);
+boolean Got_RequestFilePak(INT32 node);
-void AbortSendFiles(INT32 node);
+void SV_AbortSendFiles(INT32 node);
void CloseNetFile(void);
boolean fileexist(char *filename, time_t ptime);
-// search a file in the wadpath, return FS_FOUND when found
+// Search a file in the wadpath, return FS_FOUND when found
filestatus_t findfile(char *filename, const UINT8 *wantedmd5sum,
boolean completepath);
filestatus_t checkfilemd5(char *filename, const UINT8 *wantedmd5sum);
diff --git a/src/d_player.h b/src/d_player.h
index bd553d9fa..4e4a53a08 100644
--- a/src/d_player.h
+++ b/src/d_player.h
@@ -32,18 +32,21 @@
// Extra abilities/settings for skins (combinable stuff)
typedef enum
{
- SF_SUPER = 1, // Can turn super in singleplayer/co-op mode.
- SF_SUPERANIMS = 1<<1, // If super, use the super sonic animations
- SF_SUPERSPIN = 1<<2, // Should spin frames be played while super?
- SF_HIRES = 1<<3, // Draw the sprite 2x as small?
+ SF_SUPER = 1, // Can turn super in singleplayer/co-op mode?
+ SF_NOSUPERSPIN = 1<<1, // Should spin frames be played while super?
+ SF_NOSPINDASHDUST = 1<<2, // Spawn dust particles when charging a spindash?
+ SF_HIRES = 1<<3, // Draw the sprite at different size?
SF_NOSKID = 1<<4, // No skid particles etc
SF_NOSPEEDADJUST = 1<<5, // Skin-specific version of disablespeedadjust
SF_RUNONWATER = 1<<6, // Run on top of water FOFs?
- SF_NOJUMPSPIN = 1<<7, // SPR2_JUMP defaults to SPR2_SPRG instead of SPR2_SPIN, falling states used, and player height is full when jumping?
+ SF_NOJUMPSPIN = 1<<7, // SPR2_JUMP defaults to SPR2_SPRG instead of SPR2_ROLL, falling states used, and player height is full when jumping?
SF_NOJUMPDAMAGE = 1<<8, // Don't damage enemies, etc whilst jumping?
SF_STOMPDAMAGE = 1<<9, // Always damage enemies, etc by landing on them, no matter your vunerability?
SF_MARIODAMAGE = SF_NOJUMPDAMAGE|SF_STOMPDAMAGE, // The Mario method of being able to damage enemies, etc.
SF_MACHINE = 1<<10, // Beep boop. Are you a robot?
+ SF_DASHMODE = 1<<11, // Sonic Advance 2 style top speed increase?
+ SF_FASTEDGE = 1<<12, // Faster edge teeter?
+ SF_MULTIABILITY = 1<<13, // Revenge of Final Demo.
// free up to and including 1<<31
} skinflags_t;
@@ -64,7 +67,7 @@ typedef enum
CA_JUMPBOOST,
CA_AIRDRILL,
CA_JUMPTHOK,
- CA_DASHMODE,
+ CA_BOUNCE,
CA_TWINSPIN
} charability_t;
@@ -73,7 +76,7 @@ typedef enum
{
CA2_NONE=0,
CA2_SPINDASH,
- CA2_MULTIABILITY,
+ CA2_GUNSLINGER,
CA2_MELEE
} charability2_t;
@@ -117,10 +120,8 @@ typedef enum
// Did you get a time-over?
PF_TIMEOVER = 1<<10,
- // Ready for Super?
- PF_SUPERREADY = 1<<11,
-
// Character action status
+ PF_STARTJUMP = 1<<11,
PF_JUMPED = 1<<12,
PF_SPINNING = 1<<13,
PF_STARTDASH = 1<<14,
@@ -132,12 +133,11 @@ typedef enum
// Sliding (usually in water) like Labyrinth/Oil Ocean
PF_SLIDING = 1<<17,
- /*** NIGHTS STUFF ***/
- // Is the player in NiGHTS mode?
- PF_NIGHTSMODE = 1<<18,
- PF_TRANSFERTOCLOSEST = 1<<19,
+ // Bouncing
+ PF_BOUNCING = 1<<18,
- // Spill rings after falling
+ /*** NIGHTS STUFF ***/
+ PF_TRANSFERTOCLOSEST = 1<<19,
PF_NIGHTSFALL = 1<<20,
PF_DRILLING = 1<<21,
PF_SKIDDOWN = 1<<22,
@@ -151,9 +151,15 @@ typedef enum
PF_ANALOGMODE = 1<<26, // Analog mode?
// Can carry another player?
- PF_CANCARRY = 1<<27
+ PF_CANCARRY = 1<<27,
- // free up to and including 1<<31
+ // Used shield ability
+ PF_SHIELDABILITY = 1<<28,
+
+ // Jump damage?
+ PF_NOJUMPDAMAGE = 1<<29,
+
+ // up to 1<<31 is free
} pflags_t;
typedef enum
@@ -164,7 +170,7 @@ typedef enum
PA_EDGE,
PA_WALK,
PA_RUN,
- PA_PEEL,
+ PA_DASH,
PA_PAIN,
PA_ROLL,
PA_JUMP,
@@ -178,23 +184,34 @@ typedef enum
typedef enum
{
SH_NONE = 0,
- // Standard shields
- SH_JUMP,
- SH_ATTRACT,
- SH_ELEMENTAL,
- SH_BOMB,
- // Stupid useless unimplimented Sonic 3 shields
- SH_BUBBLEWRAP,
- SH_THUNDERCOIN,
- SH_FLAMEAURA,
- // Pity shield: the world's most basic shield ever, given to players who suck at Match
- SH_PITY,
- // The fireflower is special, it combines with other shields.
- SH_FIREFLOWER = 0x100,
- // The force shield uses the lower 8 bits to count how many hits are left.
- SH_FORCE = 0x200,
- SH_STACK = SH_FIREFLOWER,
+ // Shield flags
+ SH_PROTECTFIRE = 0x400,
+ SH_PROTECTWATER = 0x800,
+ SH_PROTECTELECTRIC = 0x1000,
+
+ // Indivisible shields
+ SH_PITY = 1, // the world's most basic shield ever, given to players who suck at Match
+ SH_WHIRLWIND,
+ SH_ARMAGEDDON,
+
+ // normal shields that use flags
+ SH_ATTRACT = SH_PROTECTELECTRIC,
+ SH_ELEMENTAL = SH_PROTECTFIRE|SH_PROTECTWATER,
+
+ // Sonic 3 shields
+ SH_FLAMEAURA = SH_PROTECTFIRE,
+ SH_BUBBLEWRAP = SH_PROTECTWATER,
+ SH_THUNDERCOIN = SH_WHIRLWIND|SH_PROTECTELECTRIC,
+
+ // The force shield uses the lower 8 bits to count how many extra hits are left.
+ SH_FORCE = 0x100,
+ SH_FORCEHP = 0xFF, // to be used as a bitmask only
+
+ // Mostly for use with Mario mode.
+ SH_FIREFLOWER = 0x200,
+
+ SH_STACK = SH_FIREFLOWER, // second-layer shields
SH_NOSTACK = ~SH_STACK
} shieldtype_t; // pw_shield
@@ -205,6 +222,10 @@ typedef enum
CR_GENERIC,
// Tails carry.
CR_PLAYER,
+ // NiGHTS mode. Not technically a CARRYING, but doesn't stack with any of the others, so might as well go here.
+ CR_NIGHTSMODE,
+ // Old Brak sucks hard, but this gimmick could be used for something better, so we might as well continue supporting it.
+ CR_BRAKGOOP,
// Specific level gimmicks.
CR_ZOOMTUBE,
CR_ROPEHANG,
@@ -244,9 +265,7 @@ typedef enum
pw_nights_helper,
pw_nights_linkfreeze,
- //for linedef exec 427
- pw_nocontrol,
- pw_ingoop, // In goop
+ pw_nocontrol, //for linedef exec 427
NUMPOWERS
} powertype_t;
@@ -297,10 +316,8 @@ typedef struct player_s
// It is updated with cmd->aiming.
angle_t aiming;
- // This is only used between levels,
- // mo->health is used during levels.
- /// \todo Remove this. We don't need a second health definition for players.
- INT32 health;
+ // player's ring count
+ INT32 rings;
SINT8 pity; // i pity the fool.
INT32 currentweapon; // current weapon selected.
@@ -324,6 +341,7 @@ typedef struct player_s
UINT8 skincolor;
INT32 skin;
+ UINT32 availabilities;
UINT32 score; // player score
fixed_t dashspeed; // dashing speed
@@ -361,8 +379,7 @@ typedef struct player_s
UINT8 gotcontinue; // Got continue from this stage?
fixed_t speed; // Player's speed (distance formula of MOMX and MOMY values)
- UINT8 jumping; // Jump counter
- UINT8 secondjump;
+ UINT8 secondjump; // Jump counter
UINT8 fly1; // Tails flying
UINT8 scoreadd; // Used for multiple enemy attack bonus
diff --git a/src/dehacked.c b/src/dehacked.c
index c4bb93ebb..2c320978d 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -371,7 +371,9 @@ static void clear_levels(void)
// (no need to set num to 0, we're freeing the entire header shortly)
Z_Free(mapheaderinfo[i]->customopts);
+ P_DeleteFlickies(i);
P_DeleteGrades(i);
+
Z_Free(mapheaderinfo[i]);
mapheaderinfo[i] = NULL;
}
@@ -380,72 +382,20 @@ static void clear_levels(void)
P_AllocMapHeader(gamemap-1);
}
-/*
-// Edits an animated texture slot on the array
-// Tails 12-27-2003
-static void readAnimTex(MYFILE *f, INT32 num)
-{
- char s[MAXLINELEN];
- char *word;
- char *word2;
- INT32 i;
-
- do {
- if (myfgets(s, sizeof s, f) != NULL)
- {
- if (s[0] == '\n') break;
-
- tmp = strchr(s, '#');
- if (tmp)
- *tmp = '\0';
- // set the value in the appropriate field
-
- word = strtok(s, " ");
- if (word)
- strupr(word);
- else
- break;
-
- word2 = strtok(NULL, " = ");
- if (word2)
- strupr(word2);
- else
- break;
-
- if (word2[strlen(word2)-1] == '\n')
- word2[strlen(word2)-1] = '\0';
-
- i = atoi(word2);
-
- if (fastcmp(word, "START"))
- strncpy(harddefs[num].startname, word2, 8);
- if (fastcmp(word, "END"))
- strncpy(harddefs[num].endname, word2, 8);
- else if (fastcmp(word, "SPEED")) harddefs[num].speed = i;
- else if (fastcmp(word, "ISTEXTURE")) harddefs[num].istexture = i;
-
- else deh_warning("readAnimTex %d: unknown word '%s'", num, word);
- }
- } while (s[0] != '\n' && !myfeof(f)); //finish when the line is empty
-}
-*/
-
-static boolean findFreeSlot(INT32 *num, UINT16 wadnum)
+static boolean findFreeSlot(INT32 *num)
{
// Send the character select entry to a free slot.
- while (*num < 32 && (!(PlayerMenu[*num].status & IT_DISABLED) || description[*num].wadnum == wadnum)) // Will kill hidden characters from other files, but that's okay.
+ while (*num < 32 && (description[*num].used))
*num = *num+1;
// No more free slots. :(
if (*num >= 32)
return false;
- PlayerMenu[*num].status = IT_CALL;
- description[*num].wadnum = wadnum;
description[*num].picname[0] = '\0'; // Redesign your logo. (See M_DrawSetupChoosePlayerMenu in m_menu.c...)
// Found one! ^_^
- return true;
+ return (description[*num].used = true);
}
// Reads a player.
@@ -477,7 +427,7 @@ static void readPlayer(MYFILE *f, INT32 num)
{
char *playertext = NULL;
- if (!slotfound && (slotfound = findFreeSlot(&num, f->wad)) == false)
+ if (!slotfound && (slotfound = findFreeSlot(&num)) == false)
goto done;
for (i = 0; i < MAXLINELEN-3; i++)
@@ -526,7 +476,7 @@ static void readPlayer(MYFILE *f, INT32 num)
if (fastcmp(word, "PICNAME"))
{
- if (!slotfound && (slotfound = findFreeSlot(&num, f->wad)) == false)
+ if (!slotfound && (slotfound = findFreeSlot(&num)) == false)
goto done;
DEH_WriteUndoline(word, &description[num].picname[0], UNDO_NONE);
@@ -534,12 +484,6 @@ static void readPlayer(MYFILE *f, INT32 num)
}
else if (fastcmp(word, "STATUS"))
{
- // Limit the status to only IT_DISABLED and IT_CALL
- if (i)
- i = IT_CALL;
- else
- i = IT_DISABLED;
-
/*
You MAY disable previous entries if you so desire...
But try to enable something that's already enabled and you will be sent to a free slot.
@@ -547,15 +491,15 @@ static void readPlayer(MYFILE *f, INT32 num)
Because of this, you are allowed to edit any previous entries you like, but only if you
signal that you are purposely doing so by disabling and then reenabling the slot.
*/
- if (i != IT_DISABLED && !slotfound && (slotfound = findFreeSlot(&num, f->wad)) == false)
+ if (i && !slotfound && (slotfound = findFreeSlot(&num)) == false)
goto done;
- DEH_WriteUndoline(word, va("%d", PlayerMenu[num].status), UNDO_NONE);
- PlayerMenu[num].status = (INT16)i;
+ DEH_WriteUndoline(word, va("%d", description[num].used), UNDO_NONE);
+ description[num].used = (!!i);
}
else if (fastcmp(word, "SKINNAME"))
{
// Send to free slot.
- if (!slotfound && (slotfound = findFreeSlot(&num, f->wad)) == false)
+ if (!slotfound && (slotfound = findFreeSlot(&num)) == false)
goto done;
DEH_WriteUndoline(word, description[num].skinname, UNDO_NONE);
@@ -990,6 +934,34 @@ static const struct {
{NULL, 0}
};
+static const struct {
+ const char *name;
+ const mobjtype_t type;
+} FLICKYTYPES[] = {
+ {"BLUEBIRD", MT_FLICKY_01},
+ {"RABBIT", MT_FLICKY_02},
+ {"CHICKEN", MT_FLICKY_03},
+ {"SEAL", MT_FLICKY_04},
+ {"PIG", MT_FLICKY_05},
+ {"CHIPMUNK", MT_FLICKY_06},
+ {"PENGUIN", MT_FLICKY_07},
+ {"FISH", MT_FLICKY_08},
+ {"RAM", MT_FLICKY_09},
+ {"PUFFIN", MT_FLICKY_10},
+ {"COW", MT_FLICKY_11},
+ {"RAT", MT_FLICKY_12},
+ {"BEAR", MT_FLICKY_13},
+ {"DOVE", MT_FLICKY_14},
+ {"CAT", MT_FLICKY_15},
+ {"CANARY", MT_FLICKY_16},
+ {"a", 0}, // End of normal flickies - a lower case character so will never fastcmp valid with uppercase tmp
+ //{"FLICKER", MT_FLICKER},
+ {"SEED", MT_SEED},
+ {NULL, 0}
+};
+
+#define MAXFLICKIES 64
+
static void readlevelheader(MYFILE *f, INT32 num)
{
char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
@@ -1088,8 +1060,82 @@ static void readlevelheader(MYFILE *f, INT32 num)
// Now go to uppercase
strupr(word2);
+ // List of flickies that are be freed in this map
+ if (fastcmp(word, "FLICKYLIST") || fastcmp(word, "ANIMALLIST"))
+ {
+ if (fastcmp(word2, "NONE"))
+ P_DeleteFlickies(num-1);
+ else if (fastcmp(word2, "DEMO"))
+ P_SetDemoFlickies(num-1);
+ else if (fastcmp(word2, "ALL"))
+ {
+ mobjtype_t tmpflickies[MAXFLICKIES];
+
+ for (mapheaderinfo[num-1]->numFlickies = 0;
+ ((mapheaderinfo[num-1]->numFlickies < MAXFLICKIES) && FLICKYTYPES[mapheaderinfo[num-1]->numFlickies].type);
+ mapheaderinfo[num-1]->numFlickies++)
+ tmpflickies[mapheaderinfo[num-1]->numFlickies] = FLICKYTYPES[mapheaderinfo[num-1]->numFlickies].type;
+
+ if (mapheaderinfo[num-1]->numFlickies) // just in case...
+ {
+ size_t newsize = sizeof(mobjtype_t) * mapheaderinfo[num-1]->numFlickies;
+ mapheaderinfo[num-1]->flickies = Z_Realloc(mapheaderinfo[num-1]->flickies, newsize, PU_STATIC, NULL);
+ M_Memcpy(mapheaderinfo[num-1]->flickies, tmpflickies, newsize);
+ }
+ }
+ else
+ {
+ mobjtype_t tmpflickies[MAXFLICKIES];
+ mapheaderinfo[num-1]->numFlickies = 0;
+ tmp = strtok(word2,",");
+ // get up to the first MAXFLICKIES flickies
+ do {
+ if (mapheaderinfo[num-1]->numFlickies == MAXFLICKIES) // never going to get above that number
+ {
+ deh_warning("Level header %d: too many flickies\n", num);
+ break;
+ }
+
+ if (fastncmp(tmp, "MT_", 3)) // support for specified mobjtypes...
+ {
+ i = get_mobjtype(tmp);
+ if (!i)
+ {
+ //deh_warning("Level header %d: unknown flicky mobj type %s\n", num, tmp); -- no need for this line as get_mobjtype complains too
+ continue;
+ }
+ tmpflickies[mapheaderinfo[num-1]->numFlickies] = i;
+ }
+ else // ...or a quick, limited selection of default flickies!
+ {
+ for (i = 0; FLICKYTYPES[i].name; i++)
+ if (fastcmp(tmp, FLICKYTYPES[i].name))
+ break;
+
+ if (!FLICKYTYPES[i].name)
+ {
+ deh_warning("Level header %d: unknown flicky selection %s\n", num, tmp);
+ continue;
+ }
+ tmpflickies[mapheaderinfo[num-1]->numFlickies] = FLICKYTYPES[i].type;
+ }
+ mapheaderinfo[num-1]->numFlickies++;
+ } while ((tmp = strtok(NULL,",")) != NULL);
+
+ if (mapheaderinfo[num-1]->numFlickies)
+ {
+ size_t newsize = sizeof(mobjtype_t) * mapheaderinfo[num-1]->numFlickies;
+ mapheaderinfo[num-1]->flickies = Z_Realloc(mapheaderinfo[num-1]->flickies, newsize, PU_STATIC, NULL);
+ // now we add them to the list!
+ M_Memcpy(mapheaderinfo[num-1]->flickies, tmpflickies, newsize);
+ }
+ else
+ deh_warning("Level header %d: no valid flicky types found\n", num);
+ }
+ }
+
// NiGHTS grades
- if (fastncmp(word, "GRADES", 6))
+ else if (fastncmp(word, "GRADES", 6))
{
UINT8 mare = (UINT8)atoi(word + 6);
@@ -1107,6 +1153,12 @@ static void readlevelheader(MYFILE *f, INT32 num)
{
deh_strlcpy(mapheaderinfo[num-1]->lvlttl, word2,
sizeof(mapheaderinfo[num-1]->lvlttl), va("Level header %d: levelname", num));
+ strlcpy(mapheaderinfo[num-1]->selectheading, word2, sizeof(mapheaderinfo[num-1]->selectheading)); // not deh_ so only complains once
+ }
+ else if (fastcmp(word, "SELECTHEADING"))
+ {
+ deh_strlcpy(mapheaderinfo[num-1]->selectheading, word2,
+ sizeof(mapheaderinfo[num-1]->selectheading), va("Level header %d: selectheading", num));
}
else if (fastcmp(word, "SCRIPTNAME"))
{
@@ -1314,6 +1366,13 @@ static void readlevelheader(MYFILE *f, INT32 num)
else
mapheaderinfo[num-1]->menuflags &= ~LF2_NOVISITNEEDED;
}
+ else if (fastcmp(word, "WIDEICON"))
+ {
+ if (i || word2[0] == 'T' || word2[0] == 'Y')
+ mapheaderinfo[num-1]->menuflags |= LF2_WIDEICON;
+ else
+ mapheaderinfo[num-1]->menuflags &= ~LF2_WIDEICON;
+ }
else
deh_warning("Level header %d: unknown word '%s'", num, word);
}
@@ -1322,6 +1381,8 @@ static void readlevelheader(MYFILE *f, INT32 num)
Z_Free(s);
}
+#undef MAXFLICKIES
+
static void readcutscenescene(MYFILE *f, INT32 num, INT32 scenenum)
{
char *s = Z_Calloc(MAXLINELEN, PU_STATIC, NULL);
@@ -1659,6 +1720,9 @@ static actionpointer_t actionpointers[] =
{{A_Pain}, "A_PAIN"},
{{A_Fall}, "A_FALL"},
{{A_MonitorPop}, "A_MONITORPOP"},
+ {{A_GoldMonitorPop}, "A_GOLDMONITORPOP"},
+ {{A_GoldMonitorRestore}, "A_GOLDMONITORRESTORE"},
+ {{A_GoldMonitorSparkle}, "A_GOLDMONITORSPARKLE"},
{{A_Look}, "A_LOOK"},
{{A_Chase}, "A_CHASE"},
{{A_FaceStabChase}, "A_FACESTABCHASE"},
@@ -1684,6 +1748,9 @@ static actionpointer_t actionpointers[] =
{{A_WaterShield}, "A_WATERSHIELD"},
{{A_ForceShield}, "A_FORCESHIELD"},
{{A_PityShield}, "A_PITYSHIELD"},
+ {{A_FlameShield}, "A_FLAMESHIELD"},
+ {{A_BubbleShield}, "A_BUBBLESHIELD"},
+ {{A_ThunderShield}, "A_THUNDERSHIELD"},
{{A_GravityBox}, "A_GRAVITYBOX"},
{{A_ScoreRise}, "A_SCORERISE"},
{{A_ParticleSpawn}, "A_PARTICLESPAWN"},
@@ -1839,6 +1906,17 @@ static actionpointer_t actionpointers[] =
{{A_BrakLobShot}, "A_BRAKLOBSHOT"},
{{A_NapalmScatter}, "A_NAPALMSCATTER"},
{{A_SpawnFreshCopy}, "A_SPAWNFRESHCOPY"},
+ {{A_FlickySpawn}, "A_FLICKYSPAWN"},
+ {{A_FlickyAim}, "A_FLICKYAIM"},
+ {{A_FlickyFly}, "A_FLICKYFLY"},
+ {{A_FlickySoar}, "A_FLICKYSOAR"},
+ {{A_FlickyCoast}, "A_FLICKYCOAST"},
+ {{A_FlickyHop}, "A_FLICKYHOP"},
+ {{A_FlickyFlounder}, "A_FLICKYFLOUNDER"},
+ {{A_FlickyCheck}, "A_FLICKYCHECK"},
+ {{A_FlickyHeightCheck}, "A_FLICKYHEIGHTCHECK"},
+ {{A_FlickyFlutter}, "A_FLICKYFLUTTER"},
+ {{A_FlameParticle}, "A_FLAMEPARTICLE"},
{{A_Boss5Jump}, "A_BOSS5JUMP"},
{{NULL}, "NONE"},
@@ -1979,10 +2057,11 @@ static void readframe(MYFILE *f, INT32 num)
Z_Free(s);
}
-static void readsound(MYFILE *f, INT32 num, const char *savesfxnames[])
+static void readsound(MYFILE *f, INT32 num)
{
char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
char *word;
+ char *word2;
char *tmp;
INT32 value;
@@ -1996,8 +2075,8 @@ static void readsound(MYFILE *f, INT32 num, const char *savesfxnames[])
tmp = strchr(s, '#');
if (tmp)
*tmp = '\0';
-
- value = searchvalue(s);
+ if (s == tmp)
+ continue; // Skip comment lines, but don't break.
word = strtok(s, " ");
if (word)
@@ -2005,21 +2084,16 @@ static void readsound(MYFILE *f, INT32 num, const char *savesfxnames[])
else
break;
-/* if (fastcmp(word, "OFFSET"))
+ word2 = strtok(NULL, " ");
+ if (word2)
+ value = atoi(word2);
+ else
{
- value -= 150360;
- if (value <= 64)
- value /= 8;
- else if (value <= 260)
- value = (value+4)/8;
- else
- value = (value+8)/8;
- if (value >= -1 && value < sfx_freeslot0 - 1)
- S_sfx[num].name = savesfxnames[value+1];
- else
- deh_warning("Sound %d: offset out of bounds", num);
+ deh_warning("No value for token %s", word);
+ continue;
}
- else */if (fastcmp(word, "SINGULAR"))
+
+ if (fastcmp(word, "SINGULAR"))
{
DEH_WriteUndoline(word, va("%d", S_sfx[num].singularity), UNDO_NONE);
S_sfx[num].singularity = value;
@@ -2034,14 +2108,17 @@ static void readsound(MYFILE *f, INT32 num, const char *savesfxnames[])
DEH_WriteUndoline(word, va("%d", S_sfx[num].pitch), UNDO_NONE);
S_sfx[num].pitch = value;
}
+ else if (fastcmp(word, "CAPTION") || fastcmp(word, "DESCRIPTION"))
+ {
+ deh_strlcpy(S_sfx[num].caption, word2,
+ sizeof(S_sfx[num].caption), va("Sound effect %d: caption", num));
+ }
else
deh_warning("Sound %d : unknown word '%s'",num,word);
}
} while (!myfeof(f));
Z_Free(s);
-
- (void)savesfxnames;
}
/** Checks if a game data file name for a mod is good.
@@ -2155,6 +2232,8 @@ static void reademblemdata(MYFILE *f, INT32 num)
emblemlocations[num-1].type = ET_NGRADE;
else if (fastcmp(word2, "NTIME"))
emblemlocations[num-1].type = ET_NTIME;
+ else if (fastcmp(word2, "MAP"))
+ emblemlocations[num-1].type = ET_MAP;
else
emblemlocations[num-1].type = (UINT8)value;
}
@@ -2325,6 +2404,7 @@ static void readunlockable(MYFILE *f, INT32 num)
DEH_WriteUndoline("VAR", va("%d", unlockables[num].variable), UNDO_NONE);
memset(&unlockables[num], 0, sizeof(unlockable_t));
+ unlockables[num].objective[0] = '/';
do
{
@@ -2666,190 +2746,6 @@ static void readconditionset(MYFILE *f, UINT8 setnum)
Z_Free(s);
}
-static void readtexture(MYFILE *f, const char *name)
-{
- char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
- char *word;
- char *word2;
- char *tmp;
- INT32 i, j, value;
- UINT16 width = 0, height = 0;
- INT16 patchcount = 0;
- texture_t *texture;
-
- do
- {
- if (myfgets(s, MAXLINELEN, f))
- {
- if (s[0] == '\n')
- break;
-
- tmp = strchr(s, '#');
- if (tmp)
- *tmp = '\0';
-
- value = searchvalue(s);
- word = strtok(s, " ");
- if (word)
- strupr(word);
- else
- break;
-
- word2 = strtok(NULL, " ");
- if (word2)
- strupr(word2);
- else
- break;
-
- // Width of the texture.
- if (fastcmp(word, "WIDTH"))
- {
- DEH_WriteUndoline(word, va("%d", width), UNDO_NONE);
- width = SHORT((UINT16)value);
- }
- // Height of the texture.
- else if (fastcmp(word, "HEIGHT"))
- {
- DEH_WriteUndoline(word, va("%d", height), UNDO_NONE);
- height = SHORT((UINT16)value);
- }
- // Number of patches the texture has.
- else if (fastcmp(word, "NUMPATCHES"))
- {
- DEH_WriteUndoline(word, va("%d", patchcount), UNDO_NONE);
- patchcount = SHORT((UINT16)value);
- }
- else
- deh_warning("readtexture: unknown word '%s'", word);
- }
- } while (!myfeof(f));
-
- // Error checking.
- if (!width)
- I_Error("Texture %s has no width!\n", name);
-
- if (!height)
- I_Error("Texture %s has no height!\n", name);
-
- if (!patchcount)
- I_Error("Texture %s has no patches!\n", name);
-
- // Allocate memory for the texture, and fill in information.
- texture = Z_Calloc(sizeof(texture_t) + (sizeof(texpatch_t) * SHORT(patchcount)), PU_STATIC, NULL);
- M_Memcpy(texture->name, name, sizeof(texture->name));
- texture->width = width;
- texture->height = height;
- texture->patchcount = patchcount;
- texture->holes = false;
- // Fill out the texture patches, to allow them to be detected
- // accurately by readpatch.
- for (i = 0; i < patchcount; i++)
- {
- texture->patches[i].originx = 0;
- texture->patches[i].originy = 0;
- texture->patches[i].wad = UINT16_MAX;
- texture->patches[i].lump = UINT16_MAX;
- }
-
- // Jump to the next empty texture entry.
- i = 0;
- while (textures[i])
- i++;
-
- // Fill the global texture buffer entries.
- j = 1;
- while (j << 1 <= texture->width)
- j <<= 1;
-
- textures[i] = texture;
- texturewidthmask[i] = j - 1;
- textureheight[i] = texture->height << FRACBITS;
-
- // Clean up.
- Z_Free(s);
-}
-
-static void readpatch(MYFILE *f, const char *name, UINT16 wad)
-{
- char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
- char *word;
- char *word2;
- char *tmp;
- INT32 i = 0, j = 0, value;
- texpatch_t patch = {0, 0, UINT16_MAX, UINT16_MAX};
-
- // Jump to the texture this patch belongs to, which,
- // coincidentally, is always the last one on the buffer cache.
- while (textures[i+1])
- i++;
-
- // Jump to the next empty patch entry.
- while (memcmp(&(textures[i]->patches[j]), &patch, sizeof(patch)))
- j++;
-
- patch.wad = wad;
- // Set the texture number, but only if the lump exists.
- if ((patch.lump = W_CheckNumForNamePwad(name, wad, 0)) == INT16_MAX)
- I_Error("readpatch: Missing patch in texture %s", textures[i]->name);
-
- // note: undoing this patch will be done by other means
- do
- {
- if (myfgets(s, MAXLINELEN, f))
- {
- if (s[0] == '\n')
- break;
-
- tmp = strchr(s, '#');
- if (tmp)
- *tmp = '\0';
-
- value = searchvalue(s);
- word = strtok(s, " ");
- if (word)
- strupr(word);
- else
- break;
-
- word2 = strtok(NULL, " ");
- if (word2)
- strupr(word2);
- else
- break;
-
- // X position of the patch in the texture.
- if (fastcmp(word, "X"))
- {
- //DEH_WriteUndoline(word, va("%d", patch->originx), UNDO_NONE);
- patch.originx = (INT16)value;
- }
- // Y position of the patch in the texture.
- else if (fastcmp(word, "Y"))
- {
- //DEH_WriteUndoline(word, va("%d", patch->originy), UNDO_NONE);
- patch.originy = (INT16)value;
- }
- else
- deh_warning("readpatch: unknown word '%s'", word);
- }
- } while (!myfeof(f));
-
- // Error checking.
- /* // Irrelevant. Origins cannot be unsigned.
- if (patch.originx == UINT16_MAX)
- I_Error("Patch %s on texture %s has no X position!\n", name, textures[i]->name);
-
- if (patch.originy == UINT16_MAX)
- I_Error("Patch %s on texture %s has no Y position!\n", name, textures[i]->name);
-*/
-
- // Set the patch as that patch number.
- textures[i]->patches[j] = patch;
-
- // Clean up.
- Z_Free(s);
-}
-
static void readmaincfg(MYFILE *f)
{
char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
@@ -3260,30 +3156,17 @@ static void ignorelines(MYFILE *f)
Z_Free(s);
}
-static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
+static void DEH_LoadDehackedFile(MYFILE *f)
{
char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
char *word;
char *word2;
INT32 i;
- // do a copy of this for cross references probleme
- //XBOXSTATIC actionf_t saveactions[NUMSTATES];
- //XBOXSTATIC const char *savesprnames[NUMSPRITES];
- XBOXSTATIC const char *savesfxnames[NUMSFX];
if (!deh_loaded)
initfreeslots();
deh_num_warning = 0;
- // save values for cross reference
- /*
- for (i = 0; i < NUMSTATES; i++)
- saveactions[i] = states[i].action;
- for (i = 0; i < NUMSPRITES; i++)
- savesprnames[i] = sprnames[i];
- */
- for (i = 0; i < NUMSFX; i++)
- savesfxnames[i] = S_sfx[i].name;
gamedataadded = false;
@@ -3360,35 +3243,19 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
if (word2[strlen(word2)-1] == '\n')
word2[strlen(word2)-1] = '\0';
i = atoi(word2);
- if (fastcmp(word, "TEXTURE"))
- {
- // Read texture from spec file.
- readtexture(f, word2);
- DEH_WriteUndoline(word, word2, UNDO_HEADER);
- }
- else if (fastcmp(word, "PATCH"))
- {
- // Read patch from spec file.
- readpatch(f, word2, wad);
- DEH_WriteUndoline(word, word2, UNDO_HEADER);
- }
- else if (fastcmp(word, "THING") || fastcmp(word, "MOBJ") || fastcmp(word, "OBJECT"))
+ if (fastcmp(word, "THING") || fastcmp(word, "MOBJ") || fastcmp(word, "OBJECT"))
{
if (i == 0 && word2[0] != '0') // If word2 isn't a number
i = get_mobjtype(word2); // find a thing by name
- if (i < NUMMOBJTYPES && i >= 0)
+ if (i < NUMMOBJTYPES && i > 0)
readthing(f, i);
else
{
- deh_warning("Thing %d out of range (0 - %d)", i, NUMMOBJTYPES-1);
+ deh_warning("Thing %d out of range (1 - %d)", i, NUMMOBJTYPES-1);
ignorelines(f);
}
DEH_WriteUndoline(word, word2, UNDO_HEADER);
}
-/* else if (fastcmp(word, "ANIMTEX"))
- {
- readAnimTex(f, i);
- }*/
else if (fastcmp(word, "LIGHT"))
{
#ifdef HWRENDER
@@ -3460,61 +3327,19 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
}
DEH_WriteUndoline(word, word2, UNDO_HEADER);
}
- // Added translations to this just in case its re-enabled
-/* else if (fastcmp(word, "POINTER"))
- {
- word = strtok(NULL, " "); // get frame
- word = strtok(NULL, ")");
- if (word)
- {
- i = atoi(word);
- if (i < NUMSTATES && i >= 0)
- {
- if (myfgets(s, MAXLINELEN, f))
- states[i].action = saveactions[searchvalue(s)];
- }
- else
- {
- deh_warning("Pointer: Frame %d doesn't exist", i);
- ignorelines(f);
- }
- }
- else
- deh_warning("pointer (Frame %d) : missing ')'", i);
- }*/
else if (fastcmp(word, "SOUND"))
{
if (i == 0 && word2[0] != '0') // If word2 isn't a number
i = get_sfx(word2); // find a sound by name
- if (i < NUMSFX && i >= 0)
- readsound(f, i, savesfxnames);
+ if (i < NUMSFX && i > 0)
+ readsound(f, i);
else
{
- deh_warning("Sound %d out of range (0 - %d)", i, NUMSFX-1);
+ deh_warning("Sound %d out of range (1 - %d)", i, NUMSFX-1);
ignorelines(f);
}
DEH_WriteUndoline(word, word2, UNDO_HEADER);
}
-/* else if (fastcmp(word, "SPRITE"))
- {
- if (i < NUMSPRITES && i >= 0)
- {
- if (myfgets(s, MAXLINELEN, f))
- {
- INT32 k;
- k = (searchvalue(s) - 151328)/8;
- if (k >= 0 && k < NUMSPRITES)
- sprnames[i] = savesprnames[k];
- else
- {
- deh_warning("Sprite %d: offset out of bounds", i);
- ignorelines(f);
- }
- }
- }
- else
- deh_warning("Sprite %d doesn't exist",i);
- }*/
else if (fastcmp(word, "HUDITEM"))
{
if (i == 0 && word2[0] != '0') // If word2 isn't a number
@@ -3601,7 +3426,10 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
// no undo support for this insanity yet
//DEH_WriteUndoline(word, word2, UNDO_HEADER);
}
- else if (fastcmp(word, "SRB2"))
+ // Last I heard this crashes the game if you try to use it
+ // so this is disabled for now
+ // -- Monster Iestyn
+/* else if (fastcmp(word, "SRB2"))
{
INT32 ver = searchvalue(strtok(NULL, "\n"));
if (ver != PATCHVERSION)
@@ -3612,6 +3440,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
// Unless you REALLY want to piss people off,
// define a custom gamedata /before/ doing this!!
// (then again, modifiedgame will prevent game data saving anyway)
+*/
else if (fastcmp(word, "CLEAR"))
{
boolean clearall = (fastcmp(word2, "ALL"));
@@ -3685,7 +3514,7 @@ void DEH_LoadDehackedLumpPwad(UINT16 wad, UINT16 lump)
W_ReadLumpPwad(wad, lump, f.data);
f.curpos = f.data;
f.data[f.size] = 0;
- DEH_LoadDehackedFile(&f, wad);
+ DEH_LoadDehackedFile(&f);
DEH_WriteUndoline(va("# uload for wad: %u, lump: %u", wad, lump), NULL, UNDO_DONE);
Z_Free(f.data);
}
@@ -3788,12 +3617,12 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
"S_PLAY_WAIT",
"S_PLAY_WALK",
"S_PLAY_RUN",
- "S_PLAY_PEEL",
+ "S_PLAY_DASH",
"S_PLAY_PAIN",
+ "S_PLAY_STUN",
"S_PLAY_DEAD",
"S_PLAY_DRWN",
- "S_PLAY_SPIN",
- "S_PLAY_DASH",
+ "S_PLAY_ROLL",
"S_PLAY_GASP",
"S_PLAY_JUMP",
"S_PLAY_SPRING",
@@ -3801,6 +3630,9 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
"S_PLAY_EDGE",
"S_PLAY_RIDE",
+ // CA2_SPINDASH
+ "S_PLAY_SPINDASH",
+
// CA_FLY/SWIM
"S_PLAY_FLY",
"S_PLAY_SWIM",
@@ -3811,30 +3643,25 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
"S_PLAY_CLING",
"S_PLAY_CLIMB",
+ // CA_FLOAT/CA_SLOWFALL
+ "S_PLAY_FLOAT",
+ "S_PLAY_FLOAT_RUN",
+
+ // CA_BOUNCE
+ "S_PLAY_BOUNCE",
+ "S_PLAY_BOUNCE_LANDING",
+
+ // CA2_GUNSLINGER
+ "S_PLAY_FIRE",
+ "S_PLAY_FIRE_FINISH",
+
// CA_TWINSPIN
"S_PLAY_TWINSPIN",
// CA2_MELEE
"S_PLAY_MELEE",
"S_PLAY_MELEE_FINISH",
-
- // SF_SUPERANIMS
- "S_PLAY_SUPER_STND",
- "S_PLAY_SUPER_WALK",
- "S_PLAY_SUPER_RUN",
- "S_PLAY_SUPER_PEEL",
- "S_PLAY_SUPER_PAIN",
- "S_PLAY_SUPER_STUN",
- "S_PLAY_SUPER_DEAD",
- "S_PLAY_SUPER_DRWN",
- "S_PLAY_SUPER_SPIN",
- "S_PLAY_SUPER_GASP",
- "S_PLAY_SUPER_JUMP",
- "S_PLAY_SUPER_SPRING",
- "S_PLAY_SUPER_FALL",
- "S_PLAY_SUPER_EDGE",
- "S_PLAY_SUPER_RIDE",
- "S_PLAY_SUPER_FLOAT",
+ "S_PLAY_MELEE_LANDING",
// SF_SUPER
"S_PLAY_SUPERTRANS1",
@@ -3873,7 +3700,7 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
"S_PLAY_NIGHTS_STAND",
"S_PLAY_NIGHTS_FLOAT",
- "S_PLAY_NIGHTS_PAIN",
+ "S_PLAY_NIGHTS_STUN",
"S_PLAY_NIGHTS_PULL",
"S_PLAY_NIGHTS_ATTACK",
@@ -4277,6 +4104,7 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
"S_GOOP1",
"S_GOOP2",
"S_GOOP3",
+ "S_GOOPTRAIL",
// Boss 3
"S_EGGMOBILE3_STND",
@@ -4649,11 +4477,11 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
"S_METALSONIC_FLOAT",
"S_METALSONIC_VECTOR",
"S_METALSONIC_STUN",
- "S_METALSONIC_BLOCK",
"S_METALSONIC_RAISE",
"S_METALSONIC_GATHER",
"S_METALSONIC_DASH",
"S_METALSONIC_BOUNCE",
+ "S_METALSONIC_BADBOUNCE",
"S_METALSONIC_SHOOT",
"S_METALSONIC_PAIN",
"S_METALSONIC_DEATH",
@@ -4832,7 +4660,7 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
"S_SPIKEBALL7",
"S_SPIKEBALL8",
- // Fire Shield's Spawn
+ // Elemental Shield's Spawn
"S_SPINFIRE1",
"S_SPINFIRE2",
"S_SPINFIRE3",
@@ -4853,7 +4681,9 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
// Starpost
"S_STARPOST_IDLE",
"S_STARPOST_FLASH",
+ "S_STARPOST_STARTSPIN",
"S_STARPOST_SPIN",
+ "S_STARPOST_ENDSPIN",
// Big floating mine
"S_BIGMINE1",
@@ -4870,170 +4700,129 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
"S_CANNONLAUNCHER2",
"S_CANNONLAUNCHER3",
- // Super Ring Box
- "S_SUPERRINGBOX",
- "S_SUPERRINGBOX1",
- "S_SUPERRINGBOX2",
- "S_SUPERRINGBOX3",
- "S_SUPERRINGBOX4",
- "S_SUPERRINGBOX5",
- "S_SUPERRINGBOX6",
+ // Monitor Miscellany
+ "S_BOXSPARKLE1",
+ "S_BOXSPARKLE2",
+ "S_BOXSPARKLE3",
- // Red Team Ring Box
- "S_REDRINGBOX",
- "S_REDRINGBOX1",
+ "S_BOX_FLICKER",
+ "S_BOX_POP1",
+ "S_BOX_POP2",
- // Blue Team Ring Box
- "S_BLUERINGBOX",
- "S_BLUERINGBOX1",
+ "S_GOLDBOX_FLICKER",
+ "S_GOLDBOX_OFF1",
+ "S_GOLDBOX_OFF2",
+ "S_GOLDBOX_OFF3",
+ "S_GOLDBOX_OFF4",
+ "S_GOLDBOX_OFF5",
+ "S_GOLDBOX_OFF6",
+ "S_GOLDBOX_OFF7",
- // Super Sneakers Box
- "S_SHTV",
- "S_SHTV1",
- "S_SHTV2",
- "S_SHTV3",
- "S_SHTV4",
- "S_SHTV5",
- "S_SHTV6",
+ // Monitor States (one per box)
+ "S_MYSTERY_BOX",
+ "S_RING_BOX",
+ "S_PITY_BOX",
+ "S_ATTRACT_BOX",
+ "S_FORCE_BOX",
+ "S_ARMAGEDDON_BOX",
+ "S_WHIRLWIND_BOX",
+ "S_ELEMENTAL_BOX",
+ "S_SNEAKERS_BOX",
+ "S_INVULN_BOX",
+ "S_1UP_BOX",
+ "S_EGGMAN_BOX",
+ "S_MIXUP_BOX",
+ "S_GRAVITY_BOX",
+ "S_RECYCLER_BOX",
+ "S_SCORE1K_BOX",
+ "S_SCORE10K_BOX",
+ "S_FLAMEAURA_BOX",
+ "S_BUBBLEWRAP_BOX",
+ "S_THUNDERCOIN_BOX",
- // Invincibility Box
- "S_PINV",
- "S_PINV1",
- "S_PINV2",
- "S_PINV3",
- "S_PINV4",
- "S_PINV5",
- "S_PINV6",
+ // Gold Repeat Monitor States (one per box)
+ "S_PITY_GOLDBOX",
+ "S_ATTRACT_GOLDBOX",
+ "S_FORCE_GOLDBOX",
+ "S_ARMAGEDDON_GOLDBOX",
+ "S_WHIRLWIND_GOLDBOX",
+ "S_ELEMENTAL_GOLDBOX",
+ "S_SNEAKERS_GOLDBOX",
+ "S_INVULN_GOLDBOX",
+ "S_EGGMAN_GOLDBOX",
+ "S_GRAVITY_GOLDBOX",
+ "S_FLAMEAURA_GOLDBOX",
+ "S_BUBBLEWRAP_GOLDBOX",
+ "S_THUNDERCOIN_GOLDBOX",
- // 1-Up Box
- "S_PRUP",
- "S_PRUP1",
- "S_PRUP2",
- "S_PRUP3",
- "S_PRUP4",
- "S_PRUP5",
- "S_PRUP6",
+ // Team Ring Boxes (these are special)
+ "S_RING_REDBOX1",
+ "S_RING_REDBOX2",
+ "S_REDBOX_POP1",
+ "S_REDBOX_POP2",
- // Ring Shield Box
- "S_YLTV",
- "S_YLTV1",
- "S_YLTV2",
- "S_YLTV3",
- "S_YLTV4",
- "S_YLTV5",
- "S_YLTV6",
+ "S_RING_BLUEBOX1",
+ "S_RING_BLUEBOX2",
+ "S_BLUEBOX_POP1",
+ "S_BLUEBOX_POP2",
- // Force Shield Box
- "S_BLTV1",
- "S_BLTV2",
- "S_BLTV3",
- "S_BLTV4",
- "S_BLTV5",
- "S_BLTV6",
- "S_BLTV7",
+ // Box Icons -- 2 states each, animation and action
+ "S_RING_ICON1",
+ "S_RING_ICON2",
- // Bomb Shield Box
- "S_BKTV1",
- "S_BKTV2",
- "S_BKTV3",
- "S_BKTV4",
- "S_BKTV5",
- "S_BKTV6",
- "S_BKTV7",
+ "S_PITY_ICON1",
+ "S_PITY_ICON2",
- // Jump Shield Box
- "S_WHTV1",
- "S_WHTV2",
- "S_WHTV3",
- "S_WHTV4",
- "S_WHTV5",
- "S_WHTV6",
- "S_WHTV7",
+ "S_ATTRACT_ICON1",
+ "S_ATTRACT_ICON2",
- // Water Shield Box
- "S_GRTV",
- "S_GRTV1",
- "S_GRTV2",
- "S_GRTV3",
- "S_GRTV4",
- "S_GRTV5",
- "S_GRTV6",
+ "S_FORCE_ICON1",
+ "S_FORCE_ICON2",
- // Pity Shield Box
- "S_PITV1",
- "S_PITV2",
- "S_PITV3",
- "S_PITV4",
- "S_PITV5",
- "S_PITV6",
- "S_PITV7",
+ "S_ARMAGEDDON_ICON1",
+ "S_ARMAGEDDON_ICON2",
- // Eggman Box
- "S_EGGTV1",
- "S_EGGTV2",
- "S_EGGTV3",
- "S_EGGTV4",
- "S_EGGTV5",
- "S_EGGTV6",
- "S_EGGTV7",
+ "S_WHIRLWIND_ICON1",
+ "S_WHIRLWIND_ICON2",
- // Teleport Box
- "S_MIXUPBOX1",
- "S_MIXUPBOX2",
- "S_MIXUPBOX3",
- "S_MIXUPBOX4",
- "S_MIXUPBOX5",
- "S_MIXUPBOX6",
- "S_MIXUPBOX7",
+ "S_ELEMENTAL_ICON1",
+ "S_ELEMENTAL_ICON2",
- // Recycler Box
- "S_RECYCLETV1",
- "S_RECYCLETV2",
- "S_RECYCLETV3",
- "S_RECYCLETV4",
- "S_RECYCLETV5",
- "S_RECYCLETV6",
- "S_RECYCLETV7",
+ "S_SNEAKERS_ICON1",
+ "S_SNEAKERS_ICON2",
- // Question Box
- "S_RANDOMBOX1",
- "S_RANDOMBOX2",
- "S_RANDOMBOX3",
+ "S_INVULN_ICON1",
+ "S_INVULN_ICON2",
- // Gravity Boots Box
- "S_GBTV1",
- "S_GBTV2",
- "S_GBTV3",
- "S_GBTV4",
- "S_GBTV5",
- "S_GBTV6",
- "S_GBTV7",
+ "S_1UP_ICON1",
+ "S_1UP_ICON2",
- // Score boxes
- "S_SCORETVA1",
- "S_SCORETVA2",
- "S_SCORETVA3",
- "S_SCORETVA4",
- "S_SCORETVA5",
- "S_SCORETVA6",
- "S_SCORETVA7",
- "S_SCORETVB1",
- "S_SCORETVB2",
- "S_SCORETVB3",
- "S_SCORETVB4",
- "S_SCORETVB5",
- "S_SCORETVB6",
- "S_SCORETVB7",
+ "S_EGGMAN_ICON1",
+ "S_EGGMAN_ICON2",
- // Monitor Explosion
- "S_MONITOREXPLOSION1",
- "S_MONITOREXPLOSION2",
+ "S_MIXUP_ICON1",
+ "S_MIXUP_ICON2",
- "S_REDMONITOREXPLOSION1",
- "S_REDMONITOREXPLOSION2",
+ "S_GRAVITY_ICON1",
+ "S_GRAVITY_ICON2",
- "S_BLUEMONITOREXPLOSION1",
- "S_BLUEMONITOREXPLOSION2",
+ "S_RECYCLER_ICON1",
+ "S_RECYCLER_ICON2",
+
+ "S_SCORE1K_ICON1",
+ "S_SCORE1K_ICON2",
+
+ "S_SCORE10K_ICON1",
+ "S_SCORE10K_ICON2",
+
+ "S_FLAMEAURA_ICON1",
+ "S_FLAMEAURA_ICON2",
+
+ "S_BUBBLEWRAP_ICON1",
+ "S_BUBBLEWRAP_ICON2",
+
+ "S_THUNDERCOIN_ICON1",
+ "S_THUNDERCOIN_ICON2",
"S_ROCKET",
@@ -5076,27 +4865,22 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
"S_DEMONFIRE6",
"S_GFZFLOWERA",
- "S_GFZFLOWERA2",
-
- "S_GFZFLOWERB1",
- "S_GFZFLOWERB2",
-
- "S_GFZFLOWERC1",
+ "S_GFZFLOWERB",
+ "S_GFZFLOWERC",
"S_BERRYBUSH",
"S_BUSH",
// THZ Plant
- "S_THZPLANT1",
- "S_THZPLANT2",
- "S_THZPLANT3",
- "S_THZPLANT4",
+ "S_THZFLOWERA",
+ "S_THZFLOWERB",
// THZ Alarm
"S_ALARM1",
// Deep Sea Gargoyle
"S_GARGOYLE",
+ "S_BIGGARGOYLE",
// DSZ Seaweed
"S_SEAWEED1",
@@ -5135,6 +4919,11 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
"S_FLAME2",
"S_FLAME3",
"S_FLAME4",
+ "S_FLAME5",
+ "S_FLAME6",
+ "S_FLAMEPARTICLE",
+
+ "S_FLAMEREST",
// Eggman Statue
"S_EGGSTATUE1",
@@ -5196,36 +4985,13 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
// Spinning flame jets
"S_FJSPINAXISA1", // Counter-clockwise
"S_FJSPINAXISA2",
- "S_FJSPINAXISA3",
- "S_FJSPINAXISA4",
- "S_FJSPINAXISA5",
- "S_FJSPINAXISA6",
- "S_FJSPINAXISA7",
- "S_FJSPINAXISA8",
- "S_FJSPINAXISA9",
- "S_FJSPINHELPERA1",
- "S_FJSPINHELPERA2",
- "S_FJSPINHELPERA3",
"S_FJSPINAXISB1", // Clockwise
"S_FJSPINAXISB2",
- "S_FJSPINAXISB3",
- "S_FJSPINAXISB4",
- "S_FJSPINAXISB5",
- "S_FJSPINAXISB6",
- "S_FJSPINAXISB7",
- "S_FJSPINAXISB8",
- "S_FJSPINAXISB9",
- "S_FJSPINHELPERB1",
- "S_FJSPINHELPERB2",
- "S_FJSPINHELPERB3",
// Blade's flame
"S_FLAMEJETFLAMEB1",
"S_FLAMEJETFLAMEB2",
"S_FLAMEJETFLAMEB3",
- "S_FLAMEJETFLAMEB4",
- "S_FLAMEJETFLAMEB5",
- "S_FLAMEJETFLAMEB6",
// Trapgoyles
"S_TRAPGOYLE",
@@ -5273,7 +5039,14 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
// Xmas-specific stuff
"S_XMASPOLE",
"S_CANDYCANE",
- "S_SNOWMAN",
+ "S_SNOWMAN", // normal
+ "S_SNOWMANHAT", // with hat + scarf
+ "S_LAMPPOST1", // normal
+ "S_LAMPPOST2", // with snow
+ "S_HANGSTAR",
+ // Xmas GFZ bushes
+ "S_XMASBERRYBUSH",
+ "S_XMASBUSH",
// Botanic Serenity's loads of scenery states
"S_BSZTALLFLOWER_RED",
@@ -5320,8 +5093,10 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
"S_BSZVINE_ORANGE",
"S_BSZSHRUB",
"S_BSZCLOVER",
- "S_BSZFISH",
- "S_BSZSUNFLOWER",
+ "S_BIG_PALMTREE_TRUNK",
+ "S_BIG_PALMTREE_TOP",
+ "S_PALMTREE_TRUNK",
+ "S_PALMTREE_TOP",
"S_DBALL1",
"S_DBALL2",
@@ -5404,6 +5179,7 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
"S_MAGN10",
"S_MAGN11",
"S_MAGN12",
+ "S_MAGN13",
"S_FORC1",
"S_FORC2",
@@ -5427,6 +5203,8 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
"S_FORC19",
"S_FORC20",
+ "S_FORC21",
+
"S_ELEM1",
"S_ELEM2",
"S_ELEM3",
@@ -5440,6 +5218,9 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
"S_ELEM11",
"S_ELEM12",
+ "S_ELEM13",
+ "S_ELEM14",
+
"S_ELEMF1",
"S_ELEMF2",
"S_ELEMF3",
@@ -5448,6 +5229,8 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
"S_ELEMF6",
"S_ELEMF7",
"S_ELEMF8",
+ "S_ELEMF9",
+ "S_ELEMF10",
"S_PITY1",
"S_PITY2",
@@ -5455,10 +5238,84 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
"S_PITY4",
"S_PITY5",
"S_PITY6",
- "S_PITY7",
- "S_PITY8",
- "S_PITY9",
- "S_PITY10",
+
+ "S_FIRS1",
+ "S_FIRS2",
+ "S_FIRS3",
+ "S_FIRS4",
+ "S_FIRS5",
+ "S_FIRS6",
+ "S_FIRS7",
+ "S_FIRS8",
+ "S_FIRS9",
+
+ "S_FIRS10",
+ "S_FIRS11",
+
+ "S_FIRSB1",
+ "S_FIRSB2",
+ "S_FIRSB3",
+ "S_FIRSB4",
+ "S_FIRSB5",
+ "S_FIRSB6",
+ "S_FIRSB7",
+ "S_FIRSB8",
+ "S_FIRSB9",
+
+ "S_FIRSB10",
+
+ "S_BUBS1",
+ "S_BUBS2",
+ "S_BUBS3",
+ "S_BUBS4",
+ "S_BUBS5",
+ "S_BUBS6",
+ "S_BUBS7",
+ "S_BUBS8",
+ "S_BUBS9",
+
+ "S_BUBS10",
+ "S_BUBS11",
+
+ "S_BUBSB1",
+ "S_BUBSB2",
+ "S_BUBSB3",
+ "S_BUBSB4",
+
+ "S_BUBSB5",
+ "S_BUBSB6",
+
+ "S_ZAPS1",
+ "S_ZAPS2",
+ "S_ZAPS3",
+ "S_ZAPS4",
+ "S_ZAPS5",
+ "S_ZAPS6",
+ "S_ZAPS7",
+ "S_ZAPS8",
+ "S_ZAPS9",
+ "S_ZAPS10",
+ "S_ZAPS11",
+ "S_ZAPS12",
+ "S_ZAPS13", // blank frame
+ "S_ZAPS14",
+ "S_ZAPS15",
+ "S_ZAPS16",
+
+ "S_ZAPSB1", // blank frame
+ "S_ZAPSB2",
+ "S_ZAPSB3",
+ "S_ZAPSB4",
+ "S_ZAPSB5",
+ "S_ZAPSB6",
+ "S_ZAPSB7",
+ "S_ZAPSB8",
+ "S_ZAPSB9",
+ "S_ZAPSB10",
+ "S_ZAPSB11", // blank frame
+
+ // Thunder spark
+ "S_THUNDERCOIN_SPARK",
// Invincibility Sparkles
"S_IVSP",
@@ -5470,43 +5327,133 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
"S_SSPK4",
"S_SSPK5",
- // Freed Birdie
- "S_BIRD1",
- "S_BIRD2",
- "S_BIRD3",
+ // Flicky-sized bubble
+ "S_FLICKY_BUBBLE",
- // Freed Bunny
- "S_BUNNY1",
- "S_BUNNY2",
- "S_BUNNY3",
- "S_BUNNY4",
- "S_BUNNY5",
- "S_BUNNY6",
- "S_BUNNY7",
- "S_BUNNY8",
- "S_BUNNY9",
- "S_BUNNY10",
+ // Bluebird
+ "S_FLICKY_01_OUT",
+ "S_FLICKY_01_FLAP1",
+ "S_FLICKY_01_FLAP2",
+ "S_FLICKY_01_FLAP3",
- // Freed Mouse
- "S_MOUSE1",
- "S_MOUSE2",
+ // Rabbit
+ "S_FLICKY_02_OUT",
+ "S_FLICKY_02_AIM",
+ "S_FLICKY_02_HOP",
+ "S_FLICKY_02_UP",
+ "S_FLICKY_02_DOWN",
- // Freed Chicken
- "S_CHICKEN1",
- "S_CHICKENHOP",
- "S_CHICKENFLY1",
- "S_CHICKENFLY2",
+ // Chicken
+ "S_FLICKY_03_OUT",
+ "S_FLICKY_03_AIM",
+ "S_FLICKY_03_HOP",
+ "S_FLICKY_03_UP",
+ "S_FLICKY_03_FLAP1",
+ "S_FLICKY_03_FLAP2",
- // Freed Cow
- "S_COW1",
- "S_COW2",
- "S_COW3",
- "S_COW4",
+ // Seal
+ "S_FLICKY_04_OUT",
+ "S_FLICKY_04_AIM",
+ "S_FLICKY_04_HOP",
+ "S_FLICKY_04_UP",
+ "S_FLICKY_04_DOWN",
+ "S_FLICKY_04_SWIM1",
+ "S_FLICKY_04_SWIM2",
+ "S_FLICKY_04_SWIM3",
+ "S_FLICKY_04_SWIM4",
- // Red Birdie in Bubble
- "S_RBIRD1",
- "S_RBIRD2",
- "S_RBIRD3",
+ // Pig
+ "S_FLICKY_05_OUT",
+ "S_FLICKY_05_AIM",
+ "S_FLICKY_05_HOP",
+ "S_FLICKY_05_UP",
+ "S_FLICKY_05_DOWN",
+
+ // Chipmunk
+ "S_FLICKY_06_OUT",
+ "S_FLICKY_06_AIM",
+ "S_FLICKY_06_HOP",
+ "S_FLICKY_06_UP",
+ "S_FLICKY_06_DOWN",
+
+ // Penguin
+ "S_FLICKY_07_OUT",
+ "S_FLICKY_07_AIML",
+ "S_FLICKY_07_HOPL",
+ "S_FLICKY_07_UPL",
+ "S_FLICKY_07_DOWNL",
+ "S_FLICKY_07_AIMR",
+ "S_FLICKY_07_HOPR",
+ "S_FLICKY_07_UPR",
+ "S_FLICKY_07_DOWNR",
+ "S_FLICKY_07_SWIM1",
+ "S_FLICKY_07_SWIM2",
+ "S_FLICKY_07_SWIM3",
+
+ // Fish
+ "S_FLICKY_08_OUT",
+ "S_FLICKY_08_AIM",
+ "S_FLICKY_08_HOP",
+ "S_FLICKY_08_FLAP1",
+ "S_FLICKY_08_FLAP2",
+ "S_FLICKY_08_FLAP3",
+ "S_FLICKY_08_FLAP4",
+ "S_FLICKY_08_SWIM1",
+ "S_FLICKY_08_SWIM2",
+ "S_FLICKY_08_SWIM3",
+ "S_FLICKY_08_SWIM4",
+
+ // Ram
+ "S_FLICKY_09_OUT",
+ "S_FLICKY_09_AIM",
+ "S_FLICKY_09_HOP",
+ "S_FLICKY_09_UP",
+ "S_FLICKY_09_DOWN",
+
+ // Puffin
+ "S_FLICKY_10_OUT",
+ "S_FLICKY_10_FLAP1",
+ "S_FLICKY_10_FLAP2",
+
+ // Cow
+ "S_FLICKY_11_OUT",
+ "S_FLICKY_11_AIM",
+ "S_FLICKY_11_RUN1",
+ "S_FLICKY_11_RUN2",
+ "S_FLICKY_11_RUN3",
+
+ // Rat
+ "S_FLICKY_12_OUT",
+ "S_FLICKY_12_AIM",
+ "S_FLICKY_12_RUN1",
+ "S_FLICKY_12_RUN2",
+ "S_FLICKY_12_RUN3",
+
+ // Bear
+ "S_FLICKY_13_OUT",
+ "S_FLICKY_13_AIM",
+ "S_FLICKY_13_HOP",
+ "S_FLICKY_13_UP",
+ "S_FLICKY_13_DOWN",
+
+ // Dove
+ "S_FLICKY_14_OUT",
+ "S_FLICKY_14_FLAP1",
+ "S_FLICKY_14_FLAP2",
+ "S_FLICKY_14_FLAP3",
+
+ // Cat
+ "S_FLICKY_15_OUT",
+ "S_FLICKY_15_AIM",
+ "S_FLICKY_15_HOP",
+ "S_FLICKY_15_UP",
+ "S_FLICKY_15_DOWN",
+
+ // Canary
+ "S_FLICKY_16_OUT",
+ "S_FLICKY_16_FLAP1",
+ "S_FLICKY_16_FLAP2",
+ "S_FLICKY_16_FLAP3",
"S_YELLOWSPRING",
"S_YELLOWSPRING2",
@@ -5620,6 +5567,20 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
"S_WATERZAP",
+ // Spindash dust
+ "S_SPINDUST1",
+ "S_SPINDUST2",
+ "S_SPINDUST3",
+ "S_SPINDUST4",
+ "S_SPINDUST_BUBBLE1",
+ "S_SPINDUST_BUBBLE2",
+ "S_SPINDUST_BUBBLE3",
+ "S_SPINDUST_BUBBLE4",
+ "S_SPINDUST_FIRE1",
+ "S_SPINDUST_FIRE2",
+ "S_SPINDUST_FIRE3",
+ "S_SPINDUST_FIRE4",
+
"S_FOG1",
"S_FOG2",
"S_FOG3",
@@ -5668,14 +5629,18 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
"S_FOUR2",
"S_FIVE2",
+ "S_LOCKON1",
+ "S_LOCKON2",
+
// Tag Sign
- "S_TTAG1",
+ "S_TTAG",
// Got Flag Sign
- "S_GOTFLAG1",
- "S_GOTFLAG2",
- "S_GOTFLAG3",
- "S_GOTFLAG4",
+ "S_GOTFLAG",
+ "S_GOTREDFLAG",
+ "S_GOTBLUEFLAG",
+
+ "S_CORK",
// Red Ring
"S_RRNG1",
@@ -5850,20 +5815,19 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
"S_FIREBALLEXP2",
"S_FIREBALLEXP3",
"S_SHELL",
- "S_SHELL1",
- "S_SHELL2",
- "S_SHELL3",
- "S_SHELL4",
- "S_PUMA1",
- "S_PUMA2",
- "S_PUMA3",
- "S_PUMA4",
- "S_PUMA5",
- "S_PUMA6",
- "S_HAMMER1",
- "S_HAMMER2",
- "S_HAMMER3",
- "S_HAMMER4",
+ "S_PUMA_START1",
+ "S_PUMA_START2",
+ "S_PUMA_UP1",
+ "S_PUMA_UP2",
+ "S_PUMA_UP3",
+ "S_PUMA_DOWN1",
+ "S_PUMA_DOWN2",
+ "S_PUMA_DOWN3",
+ "S_PUMATRAIL1",
+ "S_PUMATRAIL2",
+ "S_PUMATRAIL3",
+ "S_PUMATRAIL4",
+ "S_HAMMER",
"S_KOOPA1",
"S_KOOPA2",
"S_KOOPAFLAME1",
@@ -5992,6 +5956,7 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
"S_NIGHTOPIANHELPER6",
"S_NIGHTOPIANHELPER7",
"S_NIGHTOPIANHELPER8",
+ "S_NIGHTOPIANHELPER9",
"S_CRUMBLE1",
"S_CRUMBLE2",
@@ -6015,10 +5980,10 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
"S_SPRK16",
// Robot Explosion
+ "S_XPLD_FLICKY",
"S_XPLD1",
"S_XPLD2",
- "S_XPLD3",
- "S_XPLD4",
+ "S_XPLD_EGGTRAP",
// Underwater Explosion
"S_WPLD1",
@@ -6047,70 +6012,6 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
"S_ROCKCRUMBLEO",
"S_ROCKCRUMBLEP",
- "S_SRB1_CRAWLA1",
- "S_SRB1_CRAWLA2",
- "S_SRB1_CRAWLA3",
- "S_SRB1_CRAWLA4",
-
- "S_SRB1_BAT1",
- "S_SRB1_BAT2",
- "S_SRB1_BAT3",
- "S_SRB1_BAT4",
-
- "S_SRB1_ROBOFISH1",
- "S_SRB1_ROBOFISH2",
- "S_SRB1_ROBOFISH3",
-
- "S_SRB1_VOLCANOGUY1",
- "S_SRB1_VOLCANOGUY2",
-
- "S_SRB1_HOPPY1",
- "S_SRB1_HOPPY2",
-
- "S_SRB1_HOPPYWATER1",
- "S_SRB1_HOPPYWATER2",
-
- "S_SRB1_HOPPYSKYLAB1",
-
- "S_SRB1_MMZFLYING1",
- "S_SRB1_MMZFLYING2",
- "S_SRB1_MMZFLYING3",
- "S_SRB1_MMZFLYING4",
- "S_SRB1_MMZFLYING5",
-
- "S_SRB1_UFO1",
- "S_SRB1_UFO2",
-
- "S_SRB1_GRAYBOT1",
- "S_SRB1_GRAYBOT2",
- "S_SRB1_GRAYBOT3",
- "S_SRB1_GRAYBOT4",
- "S_SRB1_GRAYBOT5",
- "S_SRB1_GRAYBOT6",
-
- "S_SRB1_ROBOTOPOLIS1",
- "S_SRB1_ROBOTOPOLIS2",
-
- "S_SRB1_RBZBUZZ1",
- "S_SRB1_RBZBUZZ2",
-
- "S_SRB1_RBZSPIKES1",
- "S_SRB1_RBZSPIKES2",
-
- "S_SRB1_METALSONIC1",
- "S_SRB1_METALSONIC2",
- "S_SRB1_METALSONIC3",
-
- "S_SRB1_GOLDBOT1",
- "S_SRB1_GOLDBOT2",
- "S_SRB1_GOLDBOT3",
- "S_SRB1_GOLDBOT4",
- "S_SRB1_GOLDBOT5",
- "S_SRB1_GOLDBOT6",
-
- "S_SRB1_GENREX1",
- "S_SRB1_GENREX2",
-
#ifdef SEENAMES
"S_NAMECHECK",
#endif
@@ -6179,6 +6080,7 @@ static const char *const MOBJTYPE_LIST[] = { // array length left dynamic for s
"MT_BOSSTANK2",
"MT_BOSSSPIGOT",
"MT_GOOP",
+ "MT_GOOPTRAIL",
// Boss 3
"MT_EGGMOBILE3",
@@ -6261,47 +6163,70 @@ static const char *const MOBJTYPE_LIST[] = { // array length left dynamic for s
"MT_BIGAIRMINE",
"MT_CANNONLAUNCHER",
- // Monitor Boxes
- "MT_SUPERRINGBOX",
- "MT_REDRINGBOX",
- "MT_BLUERINGBOX",
- "MT_SNEAKERTV",
- "MT_INV",
- "MT_PRUP", // 1up Box
- "MT_YELLOWTV", // Attract shield TV
- "MT_BLUETV", // Force shield TV
- "MT_BLACKTV", // Bomb shield TV
- "MT_WHITETV", // Jump shield TV
- "MT_GREENTV", // Elemental shield TV
- "MT_PITYTV", // Pity shield TV
- "MT_EGGMANBOX",
- "MT_MIXUPBOX",
- "MT_RECYCLETV",
- "MT_RECYCLEICO",
- "MT_QUESTIONBOX",
- "MT_GRAVITYBOX",
- "MT_SCORETVSMALL",
- "MT_SCORETVLARGE",
-
// Monitor miscellany
- "MT_MONITOREXPLOSION",
- "MT_REDMONITOREXPLOSION",
- "MT_BLUEMONITOREXPLOSION",
- "MT_RINGICO",
- "MT_SHOESICO",
- "MT_INVCICO",
- "MT_1UPICO",
- "MT_YSHIELDICO",
- "MT_BSHIELDICO",
- "MT_KSHIELDICO",
- "MT_WSHIELDICO",
- "MT_GSHIELDICO",
- "MT_PITYSHIELDICO",
- "MT_EGGMANICO",
- "MT_MIXUPICO",
- "MT_GRAVITYICO",
- "MT_SCOREICOSMALL",
- "MT_SCOREICOLARGE",
+ "MT_BOXSPARKLE",
+
+ // Monitor boxes -- regular
+ "MT_RING_BOX",
+ "MT_PITY_BOX",
+ "MT_ATTRACT_BOX",
+ "MT_FORCE_BOX",
+ "MT_ARMAGEDDON_BOX",
+ "MT_WHIRLWIND_BOX",
+ "MT_ELEMENTAL_BOX",
+ "MT_SNEAKERS_BOX",
+ "MT_INVULN_BOX",
+ "MT_1UP_BOX",
+ "MT_EGGMAN_BOX",
+ "MT_MIXUP_BOX",
+ "MT_MYSTERY_BOX",
+ "MT_GRAVITY_BOX",
+ "MT_RECYCLER_BOX",
+ "MT_SCORE1K_BOX",
+ "MT_SCORE10K_BOX",
+ "MT_FLAMEAURA_BOX",
+ "MT_BUBBLEWRAP_BOX",
+ "MT_THUNDERCOIN_BOX",
+
+ // Monitor boxes -- repeating (big) boxes
+ "MT_PITY_GOLDBOX",
+ "MT_ATTRACT_GOLDBOX",
+ "MT_FORCE_GOLDBOX",
+ "MT_ARMAGEDDON_GOLDBOX",
+ "MT_WHIRLWIND_GOLDBOX",
+ "MT_ELEMENTAL_GOLDBOX",
+ "MT_SNEAKERS_GOLDBOX",
+ "MT_INVULN_GOLDBOX",
+ "MT_EGGMAN_GOLDBOX",
+ "MT_GRAVITY_GOLDBOX",
+ "MT_FLAMEAURA_GOLDBOX",
+ "MT_BUBBLEWRAP_GOLDBOX",
+ "MT_THUNDERCOIN_GOLDBOX",
+
+ // Monitor boxes -- special
+ "MT_RING_REDBOX",
+ "MT_RING_BLUEBOX",
+
+ // Monitor icons
+ "MT_RING_ICON",
+ "MT_PITY_ICON",
+ "MT_ATTRACT_ICON",
+ "MT_FORCE_ICON",
+ "MT_ARMAGEDDON_ICON",
+ "MT_WHIRLWIND_ICON",
+ "MT_ELEMENTAL_ICON",
+ "MT_SNEAKERS_ICON",
+ "MT_INVULN_ICON",
+ "MT_1UP_ICON",
+ "MT_EGGMAN_ICON",
+ "MT_MIXUP_ICON",
+ "MT_GRAVITY_ICON",
+ "MT_RECYCLER_ICON",
+ "MT_SCORE1K_ICON",
+ "MT_SCORE10K_ICON",
+ "MT_FLAMEAURA_ICON",
+ "MT_BUBBLEWRAP_ICON",
+ "MT_THUNDERCOIN_ICON",
// Projectiles
"MT_ROCKET",
@@ -6325,11 +6250,13 @@ static const char *const MOBJTYPE_LIST[] = { // array length left dynamic for s
"MT_BUSH",
// Techno Hill Scenery
- "MT_THZPLANT", // THZ Plant
+ "MT_THZFLOWER1",
+ "MT_THZFLOWER2",
"MT_ALARM",
// Deep Sea Scenery
"MT_GARGOYLE", // Deep Sea Gargoyle
+ "MT_BIGGARGOYLE", // Deep Sea Gargoyle (Big)
"MT_SEAWEED", // DSZ Seaweed
"MT_WATERDRIP", // Dripping Water source
"MT_WATERDROP", // Water drop from dripping water
@@ -6341,6 +6268,7 @@ static const char *const MOBJTYPE_LIST[] = { // array length left dynamic for s
// Castle Eggman Scenery
"MT_CHAIN", // CEZ Chain
"MT_FLAME", // Flame (has corona)
+ "MT_FLAMEPARTICLE",
"MT_EGGSTATUE", // Eggman Statue
"MT_MACEPOINT", // Mace rotation point
"MT_SWINGMACEPOINT", // Mace swinging point
@@ -6367,9 +6295,7 @@ static const char *const MOBJTYPE_LIST[] = { // array length left dynamic for s
"MT_FLAMEJETFLAME",
"MT_FJSPINAXISA", // Counter-clockwise
- "MT_FJSPINHELPERA",
"MT_FJSPINAXISB", // Clockwise
- "MT_FJSPINHELPERB",
"MT_FLAMEJETFLAMEB", // Blade's flame
@@ -6399,7 +6325,14 @@ static const char *const MOBJTYPE_LIST[] = { // array length left dynamic for s
// Christmas Scenery
"MT_XMASPOLE",
"MT_CANDYCANE",
- "MT_SNOWMAN",
+ "MT_SNOWMAN", // normal
+ "MT_SNOWMANHAT", // with hat + scarf
+ "MT_LAMPPOST1", // normal
+ "MT_LAMPPOST2", // with snow
+ "MT_HANGSTAR",
+ // Xmas GFZ bushes
+ "MT_XMASBERRYBUSH",
+ "MT_XMASBUSH",
// Botanic Serenity
"MT_BSZTALLFLOWER_RED",
@@ -6446,30 +6379,46 @@ static const char *const MOBJTYPE_LIST[] = { // array length left dynamic for s
"MT_BSZVINE_ORANGE",
"MT_BSZSHRUB",
"MT_BSZCLOVER",
- "MT_BSZFISH",
- "MT_BSZSUNFLOWER",
+ "MT_BIG_PALMTREE_TRUNK",
+ "MT_BIG_PALMTREE_TOP",
+ "MT_PALMTREE_TRUNK",
+ "MT_PALMTREE_TOP",
// Misc scenery
"MT_DBALL",
"MT_EGGSTATUE2",
// Powerup Indicators
- "MT_GREENORB", // Elemental shield mobj
- "MT_YELLOWORB", // Attract shield mobj
- "MT_BLUEORB", // Force shield mobj
- "MT_BLACKORB", // Armageddon shield mobj
- "MT_WHITEORB", // Whirlwind shield mobj
- "MT_PITYORB", // Pity shield mobj
- "MT_IVSP", // invincibility sparkles
+ "MT_ELEMENTAL_ORB", // Elemental shield mobj
+ "MT_ATTRACT_ORB", // Attract shield mobj
+ "MT_FORCE_ORB", // Force shield mobj
+ "MT_ARMAGEDDON_ORB", // Armageddon shield mobj
+ "MT_WHIRLWIND_ORB", // Whirlwind shield mobj
+ "MT_PITY_ORB", // Pity shield mobj
+ "MT_FLAMEAURA_ORB", // Flame shield mobj
+ "MT_BUBBLEWRAP_ORB", // Bubble shield mobj
+ "MT_THUNDERCOIN_ORB", // Thunder shield mobj
+ "MT_THUNDERCOIN_SPARK", // Thunder spark
+ "MT_IVSP", // Invincibility sparkles
"MT_SUPERSPARK", // Super Sonic Spark
- // Freed Animals
- "MT_BIRD", // Birdie freed!
- "MT_BUNNY", // Bunny freed!
- "MT_MOUSE", // Mouse
- "MT_CHICKEN", // Chicken
- "MT_COW", // Cow
- "MT_REDBIRD", // Red Birdie in Bubble
+ // Flickies
+ "MT_FLICKY_01", // Bluebird
+ "MT_FLICKY_02", // Rabbit
+ "MT_FLICKY_03", // Chicken
+ "MT_FLICKY_04", // Seal
+ "MT_FLICKY_05", // Pig
+ "MT_FLICKY_06", // Chipmunk
+ "MT_FLICKY_07", // Penguin
+ "MT_FLICKY_08", // Fish
+ "MT_FLICKY_09", // Ram
+ "MT_FLICKY_10", // Puffin
+ "MT_FLICKY_11", // Cow
+ "MT_FLICKY_12", // Rat
+ "MT_FLICKY_13", // Bear
+ "MT_FLICKY_14", // Dove
+ "MT_FLICKY_15", // Cat
+ "MT_FLICKY_16", // Canary
// Environmental Effects
"MT_RAIN", // Rain
@@ -6480,6 +6429,7 @@ static const char *const MOBJTYPE_LIST[] = { // array length left dynamic for s
"MT_MEDIUMBUBBLE", // medium bubble
"MT_EXTRALARGEBUBBLE", // extra large bubble
"MT_WATERZAP",
+ "MT_SPINDUST", // Spindash dust
"MT_TFOG",
"MT_SEED",
"MT_PARTICLE",
@@ -6489,9 +6439,9 @@ static const char *const MOBJTYPE_LIST[] = { // array length left dynamic for s
"MT_SCORE", // score logo
"MT_DROWNNUMBERS", // Drowning Timer
"MT_GOTEMERALD", // Chaos Emerald (intangible)
+ "MT_LOCKON", // Target
"MT_TAG", // Tag Sign
"MT_GOTFLAG", // Got Flag sign
- "MT_GOTFLAG2", // Got Flag sign
// Ambient Sounds
"MT_AWATERA", // Ambient Water Sound 1
@@ -6505,6 +6455,8 @@ static const char *const MOBJTYPE_LIST[] = { // array length left dynamic for s
"MT_RANDOMAMBIENT",
"MT_RANDOMAMBIENT2",
+ "MT_CORK",
+
// Ring Weapons
"MT_REDRING",
"MT_BOUNCERING",
@@ -6538,6 +6490,7 @@ static const char *const MOBJTYPE_LIST[] = { // array length left dynamic for s
"MT_FIREBALL",
"MT_SHELL",
"MT_PUMA",
+ "MT_PUMATRAIL",
"MT_HAMMER",
"MT_KOOPA",
"MT_KOOPAFLAME",
@@ -6607,22 +6560,6 @@ static const char *const MOBJTYPE_LIST[] = { // array length left dynamic for s
"MT_ROCKCRUMBLE15",
"MT_ROCKCRUMBLE16",
- "MT_SRB1_CRAWLA",
- "MT_SRB1_BAT",
- "MT_SRB1_ROBOFISH",
- "MT_SRB1_VOLCANOGUY",
- "MT_SRB1_HOPPY",
- "MT_SRB1_HOPPYWATER",
- "MT_SRB1_HOPPYSKYLAB",
- "MT_SRB1_MMZFLYING",
- "MT_SRB1_UFO",
- "MT_SRB1_GRAYBOT",
- "MT_SRB1_ROBOTOPOLIS",
- "MT_SRB1_RBZBUZZ",
- "MT_SRB1_RBZSPIKES",
- "MT_SRB1_METALSONIC",
- "MT_SRB1_GOLDBOT",
- "MT_SRB1_GENREX",
#ifdef SEENAMES
"MT_NAMECHECK",
#endif
@@ -6634,7 +6571,7 @@ static const char *const MOBJFLAG_LIST[] = {
"SHOOTABLE",
"NOSECTOR",
"NOBLOCKMAP",
- "AMBUSH",
+ "PAPERCOLLISION",
"PUSHABLE",
"BOSS",
"SPAWNCEILING",
@@ -6664,33 +6601,36 @@ static const char *const MOBJFLAG_LIST[] = {
// \tMF2_(\S+).*// (.+) --> \t"\1", // \2
static const char *const MOBJFLAG2_LIST[] = {
- "AXIS", // It's a NiGHTS axis! (For faster checking)
- "TWOD", // Moves like it's in a 2D level
- "DONTRESPAWN", // Don't respawn this object!
- "DONTDRAW", // Don't generate a vissprite
- "AUTOMATIC", // Thrown ring has automatic properties
- "RAILRING", // Thrown ring has rail properties
- "BOUNCERING", // Thrown ring has bounce properties
- "EXPLOSION", // Thrown ring has explosive properties
- "SCATTER", // Thrown ring has scatter properties
- "BEYONDTHEGRAVE",// Source of this missile has died and has since respawned.
- "SLIDEPUSH", // MF_PUSHABLE that pushes continuously.
- "CLASSICPUSH", // Drops straight down when object has negative Z.
- "STANDONME", // While not pushable, stand on me anyway.
- "INFLOAT", // Floating to a height for a move, don't auto float to target's height.
- "DEBRIS", // Splash ring from explosion ring
- "NIGHTSPULL", // Attracted from a paraloop
- "JUSTATTACKED", // can be pushed by other moving mobjs
- "FIRING", // turret fire
- "SUPERFIRE", // Firing something with Super Sonic-stopping properties. Or, if mobj has MF_MISSILE, this is the actual fire from it.
- "SHADOW", // Fuzzy draw, makes targeting harder.
- "STRONGBOX", // Flag used for "strong" random monitors.
- "OBJECTFLIP", // Flag for objects that always have flipped gravity.
- "SKULLFLY", // Special handling: skull in flight.
- "FRET", // Flashing from a previous hit
- "BOSSNOTRAP", // No Egg Trap after boss
- "BOSSFLEE", // Boss is fleeing!
- "BOSSDEAD", // Boss is dead! (Not necessarily fleeing, if a fleeing point doesn't exist.)
+ "AXIS", // It's a NiGHTS axis! (For faster checking)
+ "TWOD", // Moves like it's in a 2D level
+ "DONTRESPAWN", // Don't respawn this object!
+ "DONTDRAW", // Don't generate a vissprite
+ "AUTOMATIC", // Thrown ring has automatic properties
+ "RAILRING", // Thrown ring has rail properties
+ "BOUNCERING", // Thrown ring has bounce properties
+ "EXPLOSION", // Thrown ring has explosive properties
+ "SCATTER", // Thrown ring has scatter properties
+ "BEYONDTHEGRAVE", // Source of this missile has died and has since respawned.
+ "SLIDEPUSH", // MF_PUSHABLE that pushes continuously.
+ "CLASSICPUSH", // Drops straight down when object has negative Z.
+ "STANDONME", // While not pushable, stand on me anyway.
+ "INFLOAT", // Floating to a height for a move, don't auto float to target's height.
+ "DEBRIS", // Splash ring from explosion ring
+ "NIGHTSPULL", // Attracted from a paraloop
+ "JUSTATTACKED", // can be pushed by other moving mobjs
+ "FIRING", // turret fire
+ "SUPERFIRE", // Firing something with Super Sonic-stopping properties. Or, if mobj has MF_MISSILE, this is the actual fire from it.
+ "SHADOW", // Fuzzy draw, makes targeting harder.
+ "STRONGBOX", // Flag used for "strong" random monitors.
+ "OBJECTFLIP", // Flag for objects that always have flipped gravity.
+ "SKULLFLY", // Special handling: skull in flight.
+ "FRET", // Flashing from a previous hit
+ "BOSSNOTRAP", // No Egg Trap after boss
+ "BOSSFLEE", // Boss is fleeing!
+ "BOSSDEAD", // Boss is dead! (Not necessarily fleeing, if a fleeing point doesn't exist.)
+ "AMBUSH", // Alternate behaviour typically set by MTF_AMBUSH
+ "LINKDRAW", // Draw vissprite of mobj immediately before/after tracer's vissprite (dependent on dispoffset and position)
+ "SHIELD", // Thinker calls P_AddShield/P_ShieldLook (must be partnered with MF_SCENERY to use)
NULL
};
@@ -6740,10 +6680,8 @@ static const char *const PLAYERFLAG_LIST[] = {
// Did you get a time-over?
"TIMEOVER",
- // Ready for Super?
- "SUPERREADY",
-
// Character action status
+ "STARTJUMP",
"JUMPED",
"SPINNING",
"STARTDASH",
@@ -6755,12 +6693,11 @@ static const char *const PLAYERFLAG_LIST[] = {
// Sliding (usually in water) like Labyrinth/Oil Ocean
"SLIDING",
- /*** NIGHTS STUFF ***/
- // Is the player in NiGHTS mode?
- "NIGHTSMODE",
- "TRANSFERTOCLOSEST",
+ // Bouncing
+ "BOUNCING",
- // Spill rings after falling
+ /*** NIGHTS STUFF ***/
+ "TRANSFERTOCLOSEST",
"NIGHTSFALL",
"DRILLING",
"SKIDDOWN",
@@ -6772,6 +6709,9 @@ static const char *const PLAYERFLAG_LIST[] = {
/*** misc ***/
"FORCESTRAFE", // Translate turn inputs into strafe inputs
"ANALOGMODE", // Analog mode?
+ "CANCARRY", // Can carry?
+ "SHIELDABILITY", // Thokked with shield ability
+ "NOJUMPDAMAGE", // No jump damage
NULL // stop loop here.
};
@@ -6920,8 +6860,7 @@ static const char *const POWERS_LIST[] = {
"NIGHTS_LINKFREEZE",
//for linedef exec 427
- "NOCONTROL",
- "INGOOP" // In goop
+ "NOCONTROL"
};
static const char *const HUDITEMS_LIST[] = {
@@ -7013,10 +6952,15 @@ struct {
// Frame settings
{"FF_FRAMEMASK",FF_FRAMEMASK},
+ {"FF_SPR2SUPER",FF_SPR2SUPER},
{"FF_SPR2ENDSTATE",FF_SPR2ENDSTATE},
- {"FF_MIDDLESTARTCHANCE",FF_MIDDLESTARTCHANCE},
+ {"FF_SPR2MIDSTART",FF_SPR2MIDSTART},
{"FF_ANIMATE",FF_ANIMATE},
+ {"FF_RANDOMANIM",FF_RANDOMANIM},
+ {"FF_GLOBALANIM",FF_GLOBALANIM},
{"FF_FULLBRIGHT",FF_FULLBRIGHT},
+ {"FF_VERTICALFLIP",FF_VERTICALFLIP},
+ {"FF_PAPERSPRITE",FF_PAPERSPRITE},
{"FF_TRANSMASK",FF_TRANSMASK},
{"FF_TRANSSHIFT",FF_TRANSSHIFT},
// new preshifted translucency (used in source)
@@ -7079,6 +7023,7 @@ struct {
{"LF2_RECORDATTACK",LF2_RECORDATTACK},
{"LF2_NIGHTSATTACK",LF2_NIGHTSATTACK},
{"LF2_NOVISITNEEDED",LF2_NOVISITNEEDED},
+ {"LF2_WIDEICON",LF2_WIDEICON},
// NiGHTS grades
{"GRADE_F",GRADE_F},
@@ -7112,20 +7057,27 @@ struct {
{"PRECIP_STORM_NOSTRIKES",PRECIP_STORM_NOSTRIKES},
// Shields
- // These ones use the lower 8 bits
{"SH_NONE",SH_NONE},
- {"SH_JUMP",SH_JUMP},
+ // Shield flags
+ {"SH_PROTECTFIRE",SH_PROTECTFIRE},
+ {"SH_PROTECTWATER",SH_PROTECTWATER},
+ {"SH_PROTECTELECTRIC",SH_PROTECTELECTRIC},
+ // Indivisible shields
+ {"SH_PITY",SH_PITY},
+ {"SH_WHIRLWIND",SH_WHIRLWIND},
+ {"SH_ARMAGEDDON",SH_ARMAGEDDON},
+ // normal shields that use flags
{"SH_ATTRACT",SH_ATTRACT},
{"SH_ELEMENTAL",SH_ELEMENTAL},
- {"SH_BOMB",SH_BOMB},
+ // Sonic 3 shields
+ {"SH_FLAMEAURA",SH_FLAMEAURA},
{"SH_BUBBLEWRAP",SH_BUBBLEWRAP},
{"SH_THUNDERCOIN",SH_THUNDERCOIN},
- {"SH_FLAMEAURA",SH_FLAMEAURA},
- {"SH_PITY",SH_PITY},
- // These ones are special and use the upper bits
- {"SH_FIREFLOWER",SH_FIREFLOWER}, // Lower bits are a normal shield stacked on top of the fire flower
- {"SH_FORCE",SH_FORCE}, // Lower bits are how many hits left, 0 is the last hit
- // Stack masks
+ // The force shield uses the lower 8 bits to count how many extra hits are left.
+ {"SH_FORCE",SH_FORCE},
+ {"SH_FORCEHP",SH_FORCEHP}, // to be used as a bitmask only
+ // Mostly for use with Mario mode.
+ {"SH_FIREFLOWER", SH_FIREFLOWER},
{"SH_STACK",SH_STACK},
{"SH_NOSTACK",SH_NOSTACK},
@@ -7133,6 +7085,8 @@ struct {
{"CR_NONE",CR_NONE},
{"CR_GENERIC",CR_GENERIC},
{"CR_PLAYER",CR_PLAYER},
+ {"CR_NIGHTSMODE",CR_NIGHTSMODE},
+ {"CR_BRAKGOOP",CR_BRAKGOOP},
{"CR_ZOOMTUBE",CR_ZOOMTUBE},
{"CR_ROPEHANG",CR_ROPEHANG},
{"CR_MACESPIN",CR_MACESPIN},
@@ -7148,8 +7102,8 @@ struct {
// Character flags (skinflags_t)
{"SF_SUPER",SF_SUPER},
- {"SF_SUPERANIMS",SF_SUPERANIMS},
- {"SF_SUPERSPIN",SF_SUPERSPIN},
+ {"SF_NOSUPERSPIN",SF_NOSUPERSPIN},
+ {"SF_NOSPINDASHDUST",SF_NOSPINDASHDUST},
{"SF_HIRES",SF_HIRES},
{"SF_NOSKID",SF_NOSKID},
{"SF_NOSPEEDADJUST",SF_NOSPEEDADJUST},
@@ -7159,6 +7113,9 @@ struct {
{"SF_STOMPDAMAGE",SF_STOMPDAMAGE},
{"SF_MARIODAMAGE",SF_MARIODAMAGE},
{"SF_MACHINE",SF_MACHINE},
+ {"SF_DASHMODE",SF_DASHMODE},
+ {"SF_FASTEDGE",SF_FASTEDGE},
+ {"SF_MULTIABILITY",SF_MULTIABILITY},
// Character abilities!
// Primary
@@ -7176,12 +7133,12 @@ struct {
{"CA_JUMPBOOST",CA_JUMPBOOST},
{"CA_AIRDRILL",CA_AIRDRILL},
{"CA_JUMPTHOK",CA_JUMPTHOK},
- {"CA_DASHMODE",CA_DASHMODE},
+ {"CA_BOUNCE",CA_BOUNCE},
{"CA_TWINSPIN",CA_TWINSPIN},
// Secondary
{"CA2_NONE",CA2_NONE}, // now slot 0!
{"CA2_SPINDASH",CA2_SPINDASH},
- {"CA2_MULTIABILITY",CA2_MULTIABILITY},
+ {"CA2_GUNSLINGER",CA2_GUNSLINGER},
{"CA2_MELEE",CA2_MELEE},
// Sound flags
@@ -7192,7 +7149,12 @@ struct {
{"SF_X8AWAYSOUND",SF_X8AWAYSOUND},
{"SF_NOINTERRUPT",SF_NOINTERRUPT},
{"SF_X2AWAYSOUND",SF_X2AWAYSOUND},
-
+
+ // Map emblem var flags
+ {"ME_ALLEMERALDS",ME_ALLEMERALDS},
+ {"ME_ULTIMATE",ME_ULTIMATE},
+ {"ME_PERFECT",ME_PERFECT},
+
#ifdef HAVE_BLUA
// p_local.h constants
{"FLOATSPEED",FLOATSPEED},
@@ -7207,6 +7169,22 @@ struct {
{"PAL_MIXUP",PAL_MIXUP},
{"PAL_RECYCLE",PAL_RECYCLE},
{"PAL_NUKE",PAL_NUKE},
+ // for P_DamageMobj
+ //// Damage types
+ {"DMG_WATER",DMG_WATER},
+ {"DMG_FIRE",DMG_FIRE},
+ {"DMG_ELECTRIC",DMG_ELECTRIC},
+ {"DMG_SPIKE",DMG_SPIKE},
+ {"DMG_NUKE",DMG_NUKE},
+ //// Death types
+ {"DMG_INSTAKILL",DMG_INSTAKILL},
+ {"DMG_DROWNED",DMG_DROWNED},
+ {"DMG_SPACEDROWN",DMG_SPACEDROWN},
+ {"DMG_DEATHPIT",DMG_DEATHPIT},
+ {"DMG_CRUSHED",DMG_CRUSHED},
+ {"DMG_SPECTATOR",DMG_SPECTATOR},
+ //// Masks
+ {"DMG_DEATHMASK",DMG_DEATHMASK},
// Gametypes, for use with global var "gametype"
{"GT_COOP",GT_COOP},
@@ -7229,7 +7207,7 @@ struct {
{"PA_EDGE",PA_EDGE},
{"PA_WALK",PA_WALK},
{"PA_RUN",PA_RUN},
- {"PA_PEEL",PA_PEEL},
+ {"PA_DASH",PA_DASH},
{"PA_PAIN",PA_PAIN},
{"PA_ROLL",PA_ROLL},
{"PA_JUMP",PA_JUMP},
@@ -7315,6 +7293,11 @@ struct {
{"FF_COLORMAPONLY",FF_COLORMAPONLY}, ///< Only copy the colormap, not the lightlevel
{"FF_GOOWATER",FF_GOOWATER}, ///< Used with ::FF_SWIMMABLE. Makes thick bouncey goop.
+#ifdef HAVE_LUA_SEGS
+ // Node flags
+ {"NF_SUBSECTOR",NF_SUBSECTOR}, // Indicate a leaf.
+#endif
+
// Angles
{"ANG1",ANG1},
{"ANG2",ANG2},
@@ -7413,6 +7396,7 @@ struct {
{"V_70TRANS",V_70TRANS},
{"V_80TRANS",V_80TRANS},
{"V_90TRANS",V_90TRANS},
+ {"V_STATIC",V_STATIC},
{"V_HUDTRANSHALF",V_HUDTRANSHALF},
{"V_HUDTRANS",V_HUDTRANS},
{"V_HUDTRANSDOUBLE",V_HUDTRANSDOUBLE},
@@ -7460,7 +7444,7 @@ static mobjtype_t get_mobjtype(const char *word)
if (fastcmp(word, MOBJTYPE_LIST[i]+3))
return i;
deh_warning("Couldn't find mobjtype named 'MT_%s'",word);
- return MT_BLUECRAWLA;
+ return MT_NULL;
}
static statenum_t get_state(const char *word)
diff --git a/src/djgppdos/i_system.c b/src/djgppdos/i_system.c
index 854d68f4d..dae9ed16e 100644
--- a/src/djgppdos/i_system.c
+++ b/src/djgppdos/i_system.c
@@ -1721,6 +1721,18 @@ INT32 I_PutEnv(char *variable)
return putenv(variable);
}
+INT32 I_ClipboardCopy(const char *data, size_t size)
+{
+ (void)data;
+ (void)size;
+ return -1;
+}
+
+char *I_ClipboardPaste(void)
+{
+ return NULL;
+}
+
const CPUInfoFlags *I_CPUInfo(void)
{
static CPUInfoFlags DOS_CPUInfo;
diff --git a/src/djgppdos/i_video.c b/src/djgppdos/i_video.c
index 612c72215..6a7641174 100644
--- a/src/djgppdos/i_video.c
+++ b/src/djgppdos/i_video.c
@@ -90,6 +90,10 @@ static unsigned long nombre = NEWTICRATE*10;
static void I_BlitScreenVesa1(void); //see later
void I_FinishUpdate (void)
{
+ // draw captions if enabled
+ if (cv_closedcaptioning.value)
+ SCR_ClosedCaptions();
+
// draw FPS if enabled
if (cv_ticrate.value)
SCR_DisplayTicRate();
diff --git a/src/doomdef.h b/src/doomdef.h
index 3f504d25f..16d7fa218 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -214,7 +214,7 @@ extern FILE *logstream;
// it's only for detection of the version the player is using so the MS can alert them of an update.
// Only set it higher, not lower, obviously.
// Note that we use this to help keep internal testing in check; this is why v2.1.0 is not version "1".
-#define MODVERSION 21
+#define MODVERSION 24
// =========================================================================
@@ -222,7 +222,7 @@ extern FILE *logstream;
// NOTE: it needs more than this to increase the number of players...
#define MAXPLAYERS 32
-#define MAXSKINS MAXPLAYERS
+#define MAXSKINS 32
#define PLAYERSMASK (MAXPLAYERS-1)
#define MAXPLAYERNAME 21
@@ -407,6 +407,7 @@ void M_StartupLocale(void);
extern void *(*M_Memcpy)(void* dest, const void* src, size_t n) FUNCNONNULL;
char *va(const char *format, ...) FUNCPRINTF;
char *M_GetToken(const char *inputString);
+void M_UnGetToken(void);
char *sizeu1(size_t num);
char *sizeu2(size_t num);
char *sizeu3(size_t num);
@@ -435,6 +436,9 @@ extern INT32 cv_debug;
// Misc stuff for later...
// =======================
+// Modifier key variables, accessible anywhere
+extern UINT8 shiftdown, ctrldown, altdown;
+
// if we ever make our alloc stuff...
#define ZZ_Alloc(x) Z_Malloc(x, PU_STATIC, NULL)
@@ -536,4 +540,14 @@ extern const char *compdate, *comptime, *comprevision, *compbranch;
/// \note You should leave this enabled unless you're working with a future SRB2 version.
#define MUSICSLOT_COMPATIBILITY
+/// Experimental attempts at preventing MF_PAPERCOLLISION objects from getting stuck in walls.
+//#define PAPER_COLLISIONCORRECTION
+
+/// Hudname padding.
+#define SKINNAMEPADDING
+
+/// Handle touching sector specials in P_PlayerAfterThink instead of P_PlayerThink.
+/// \note Required for proper collision with moving sloped surfaces that have sector specials on them.
+//#define SECTORSPECIALSAFTERTHINK
+
#endif // __DOOMDEF__
diff --git a/src/doomstat.h b/src/doomstat.h
index 8072a1552..a3b07c9cb 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -241,6 +241,12 @@ typedef struct
UINT8 levelflags; ///< LF_flags: merged eight booleans into one UINT8 for space, see below
UINT8 menuflags; ///< LF2_flags: options that affect record attack / nights mode menus
+ char selectheading[22]; ///< Level select heading. Allows for controllable grouping.
+
+ // Freed animals stuff.
+ UINT8 numFlickies; ///< Internal. For freed flicky support.
+ mobjtype_t *flickies; ///< List of freeable flickies in this level. Allocated dynamically for space reasons. Be careful.
+
// NiGHTS stuff.
UINT8 numGradedMares; ///< Internal. For grade support.
nightsgrades_t *grades; ///< NiGHTS grades. Allocated dynamically for space reasons. Be careful.
@@ -263,6 +269,7 @@ typedef struct
#define LF2_RECORDATTACK 4 ///< Show this map in Time Attack
#define LF2_NIGHTSATTACK 8 ///< Show this map in NiGHTS mode menu
#define LF2_NOVISITNEEDED 16 ///< Available in time attack/nights mode without visiting the level
+#define LF2_WIDEICON 32 ///< If you're in a circumstance where it fits, use a wide map icon
extern mapheader_t* mapheaderinfo[NUMMAPS];
@@ -307,7 +314,7 @@ enum GameType
NUMGAMETYPES
};
-// If you alter this list, update gametype_cons_t in m_menu.c
+// If you alter this list, update dehacked.c, and gametype_cons_t and MISC_ChangeGameTypeMenu in m_menu.c
extern tic_t totalplaytime;
@@ -372,6 +379,7 @@ nightsdata_t ntemprecords;
extern UINT32 token; ///< Number of tokens collected in a level
extern UINT32 tokenlist; ///< List of tokens collected
+extern boolean gottoken; ///< Did you get a token? Used for end of act
extern INT32 tokenbits; ///< Used for setting token bits
extern INT32 sstimer; ///< Time allotted in the special stage
extern UINT32 bluescore; ///< Blue Team Scores
@@ -445,19 +453,17 @@ extern mapthing_t *redctfstarts[MAXPLAYERS]; // CTF
#if defined (macintosh)
#define DEBFILE(msg) I_OutputMsg(msg)
-extern FILE *debugfile;
#else
#define DEBUGFILE
#ifdef DEBUGFILE
#define DEBFILE(msg) { if (debugfile) { fputs(msg, debugfile); fflush(debugfile); } }
-extern FILE *debugfile;
#else
#define DEBFILE(msg) {}
-extern FILE *debugfile;
#endif
#endif
#ifdef DEBUGFILE
+extern FILE *debugfile;
extern INT32 debugload;
#endif
diff --git a/src/dummy/i_system.c b/src/dummy/i_system.c
index c1e48b60b..a3fe3077c 100644
--- a/src/dummy/i_system.c
+++ b/src/dummy/i_system.c
@@ -162,6 +162,18 @@ INT32 I_PutEnv(char *variable)
return -1;
}
+INT32 I_ClipboardCopy(const char *data, size_t size)
+{
+ (void)data;
+ (void)size;
+ return -1;
+}
+
+char *I_ClipboardPaste(void)
+{
+ return NULL;
+}
+
void I_RegisterSysCommands(void) {}
#include "../sdl/dosstr.c"
diff --git a/src/f_finale.c b/src/f_finale.c
index 81c530e6a..db497daf7 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -434,7 +434,6 @@ void F_StartIntro(void)
G_SetGamestate(GS_INTRO);
gameaction = ga_nothing;
- playerdeadview = false;
paused = false;
CON_ToggleOff();
CON_ClearHUD();
@@ -559,7 +558,7 @@ static void F_IntroDrawScene(void)
if (finalecount < 4)
S_StopMusic();
if (finalecount == 4)
- S_ChangeMusicInternal("stjr", false);
+ S_ChangeMusicInternal("_stjr", false);
x = (BASEVIDWIDTH<s.red+1)<>FRACBITS;
}
@@ -337,7 +337,8 @@ void F_RunWipe(UINT8 wipetype, boolean drawMenu)
UINT8 wipeframe = 0;
fademask_t *fmask;
- paldiv = FixedDiv(257<angleturn = (INT16)(cmd->angleturn + angleturn[tspeed]);
}
- if (cv_analog.value || twodlevel
+ if (twodlevel
|| (player->mo && (player->mo->flags2 & MF2_TWOD))
|| (!demoplayback && (player->climbing
- || (player->pflags & PF_NIGHTSMODE)
- || (player->pflags & PF_SLIDING)
- || (player->pflags & PF_FORCESTRAFE)))) // Analog
+ || (player->powers[pw_carry] == CR_NIGHTSMODE)
+ || (player->pflags & (PF_SLIDING|PF_FORCESTRAFE))))) // Analog
forcestrafe = true;
- if (forcestrafe) // Analog
+ if (forcestrafe)
{
if (turnright)
side += sidemove[speed];
@@ -1031,6 +1031,13 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics)
side += ((axis * sidemove[1]) >> 10);
}
}
+ else if (cv_analog.value) // Analog
+ {
+ if (turnright)
+ cmd->buttons |= BT_CAMRIGHT;
+ if (turnleft)
+ cmd->buttons |= BT_CAMLEFT;
+ }
else
{
if (turnright)
@@ -1118,15 +1125,6 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics)
if (PLAYER1INPUTDOWN(gc_use))
cmd->buttons |= BT_USE;
- // Camera Controls
- if (cv_debug || cv_analog.value || demoplayback || objectplacing || player->pflags & PF_NIGHTSMODE)
- {
- if (PLAYER1INPUTDOWN(gc_camleft))
- cmd->buttons |= BT_CAMLEFT;
- if (PLAYER1INPUTDOWN(gc_camright))
- cmd->buttons |= BT_CAMRIGHT;
- }
-
if (PLAYER1INPUTDOWN(gc_camreset))
{
if (camera.chase && !resetdown)
@@ -1188,10 +1186,19 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics)
if (!mouseaiming && cv_mousemove.value)
forward += mousey;
- if (cv_analog.value ||
- (!demoplayback && (player->climbing
+ if ((!demoplayback && (player->climbing
|| (player->pflags & PF_SLIDING)))) // Analog for mouse
side += mousex*2;
+ else if (cv_analog.value)
+ {
+ if (mousex)
+ {
+ if (mousex > 0)
+ cmd->buttons |= BT_CAMRIGHT;
+ else
+ cmd->buttons |= BT_CAMLEFT;
+ }
+ }
else
cmd->angleturn = (INT16)(cmd->angleturn - (mousex*8));
@@ -1226,9 +1233,10 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics)
cmd->sidemove = (SINT8)(cmd->sidemove + side);
if (cv_analog.value) {
- cmd->angleturn = (INT16)(thiscam->angle >> 16);
if (player->awayviewtics)
cmd->angleturn = (INT16)(player->awayviewmobj->angle >> 16);
+ else
+ cmd->angleturn = (INT16)(thiscam->angle >> 16);
}
else
{
@@ -1302,12 +1310,11 @@ void G_BuildTiccmd2(ticcmd_t *cmd, INT32 realtics)
if (turnleft)
cmd->angleturn = (INT16)(cmd->angleturn + angleturn[tspeed]);
}
- if (cv_analog2.value || twodlevel
+ if (twodlevel
|| (player->mo && (player->mo->flags2 & MF2_TWOD))
|| player->climbing
- || (player->pflags & PF_NIGHTSMODE)
- || (player->pflags & PF_SLIDING)
- || (player->pflags & PF_FORCESTRAFE)) // Analog
+ || (player->powers[pw_carry] == CR_NIGHTSMODE)
+ || (player->pflags & (PF_SLIDING|PF_FORCESTRAFE))) // Analog
forcestrafe = true;
if (forcestrafe) // Analog
{
@@ -1322,6 +1329,13 @@ void G_BuildTiccmd2(ticcmd_t *cmd, INT32 realtics)
side += ((axis * sidemove[1]) >> 10);
}
}
+ else if (cv_analog2.value) // Analog
+ {
+ if (turnright)
+ cmd->buttons |= BT_CAMRIGHT;
+ if (turnleft)
+ cmd->buttons |= BT_CAMLEFT;
+ }
else
{
if (turnright)
@@ -1406,15 +1420,6 @@ void G_BuildTiccmd2(ticcmd_t *cmd, INT32 realtics)
if (PLAYER2INPUTDOWN(gc_use))
cmd->buttons |= BT_USE;
- // Camera Controls
- if (cv_debug || cv_analog2.value || player->pflags & PF_NIGHTSMODE)
- {
- if (PLAYER2INPUTDOWN(gc_camleft))
- cmd->buttons |= BT_CAMLEFT;
- if (PLAYER2INPUTDOWN(gc_camright))
- cmd->buttons |= BT_CAMRIGHT;
- }
-
if (PLAYER2INPUTDOWN(gc_camreset))
{
if (camera2.chase && !resetdown)
@@ -1476,9 +1481,19 @@ void G_BuildTiccmd2(ticcmd_t *cmd, INT32 realtics)
if (!mouseaiming && cv_mousemove2.value)
forward += mouse2y;
- if (cv_analog2.value || player->climbing
+ if (player->climbing
|| (player->pflags & PF_SLIDING)) // Analog for mouse
side += mouse2x*2;
+ else if (cv_analog2.value)
+ {
+ if (mouse2x)
+ {
+ if (mouse2x > 0)
+ cmd->buttons |= BT_CAMRIGHT;
+ else
+ cmd->buttons |= BT_CAMLEFT;
+ }
+ }
else
cmd->angleturn = (INT16)(cmd->angleturn - (mouse2x*8));
@@ -1526,9 +1541,10 @@ void G_BuildTiccmd2(ticcmd_t *cmd, INT32 realtics)
}
if (cv_analog2.value) {
- cmd->angleturn = (INT16)(thiscam->angle >> 16);
if (player->awayviewtics)
cmd->angleturn = (INT16)(player->awayviewmobj->angle >> 16);
+ else
+ cmd->angleturn = (INT16)(thiscam->angle >> 16);
}
else
{
@@ -1565,11 +1581,6 @@ static void Analog_OnChange(void)
// cameras are not initialized at this point
- if (leveltime > 1)
- CV_SetValue(&cv_cam_dist, 128);
- if (cv_analog.value || demoplayback)
- CV_SetValue(&cv_cam_dist, 192);
-
if (!cv_chasecam.value && cv_analog.value) {
CV_SetValue(&cv_analog, 0);
return;
@@ -1590,11 +1601,6 @@ static void Analog2_OnChange(void)
// cameras are not initialized at this point
- if (leveltime > 1)
- CV_SetValue(&cv_cam2_dist, 128);
- if (cv_analog2.value)
- CV_SetValue(&cv_cam2_dist, 192);
-
if (!cv_chasecam2.value && cv_analog2.value) {
CV_SetValue(&cv_analog2, 0);
return;
@@ -2081,6 +2087,7 @@ void G_PlayerReborn(INT32 player)
UINT8 mare;
UINT8 skincolor;
INT32 skin;
+ UINT32 availabilities;
tic_t jointime;
boolean spectator;
INT16 bot;
@@ -2105,6 +2112,7 @@ void G_PlayerReborn(INT32 player)
skincolor = players[player].skincolor;
skin = players[player].skin;
+ availabilities = players[player].availabilities;
camerascale = players[player].camerascale;
shieldscale = players[player].shieldscale;
charability = players[player].charability;
@@ -2150,6 +2158,7 @@ void G_PlayerReborn(INT32 player)
// save player config truth reborn
p->skincolor = skincolor;
p->skin = skin;
+ p->availabilities = availabilities;
p->camerascale = camerascale;
p->shieldscale = shieldscale;
p->charability = charability;
@@ -2193,7 +2202,7 @@ void G_PlayerReborn(INT32 player)
p->pflags |= PF_JUMPDOWN;
p->playerstate = PST_LIVE;
- p->health = 1; // 0 rings
+ p->rings = 0; // 0 rings
p->panim = PA_IDLE; // standing animation
if ((netgame || multiplayer) && !p->spectator)
@@ -2300,6 +2309,9 @@ void G_SpawnPlayer(INT32 playernum, boolean starpost)
if (starpost) //Don't even bother with looking for a place to spawn.
{
P_MovePlayerToStarpost(playernum);
+#ifdef HAVE_BLUA
+ LUAh_PlayerSpawn(&players[playernum]); // Lua hook for player spawning :)
+#endif
return;
}
@@ -2624,7 +2636,7 @@ void G_ExitLevel(void)
CONS_Printf(M_GetText("The round has ended.\n"));
// Remove CEcho text on round end.
- HU_DoCEcho("");
+ HU_ClearCEcho();
}
}
@@ -2772,7 +2784,6 @@ static INT16 RandMap(INT16 tolflags, INT16 pprevmap)
static void G_DoCompleted(void)
{
INT32 i;
- boolean gottoken = false;
tokenlist = 0; // Reset the list
@@ -2858,10 +2869,9 @@ static void G_DoCompleted(void)
if (nextmap >= 1100-1 && nextmap <= 1102-1 && (gametype == GT_RACE || gametype == GT_COMPETITION))
nextmap = (INT16)(spstage_start-1);
- if (gametype == GT_COOP && token)
+ if ((gottoken = (gametype == GT_COOP && token)))
{
token--;
- gottoken = true;
if (!(emeralds & EMERALD1))
nextmap = (INT16)(sstage_start - 1); // Special Stage 1
@@ -2900,7 +2910,7 @@ static void G_DoCompleted(void)
if (nextmap < NUMMAPS && !mapheaderinfo[nextmap])
P_AllocMapHeader(nextmap);
- if (skipstats)
+ if (skipstats && !modeattacking) // Don't skip stats if we're in record attack
G_AfterIntermission();
else
{
@@ -2920,7 +2930,7 @@ void G_AfterIntermission(void)
if (nextmap < 1100-1)
G_NextLevel();
else
- Y_EndGame();
+ G_EndGame();
}
}
@@ -3006,6 +3016,38 @@ static void G_DoContinued(void)
gameaction = ga_nothing;
}
+//
+// G_EndGame (formerly Y_EndGame)
+// Frankly this function fits better in g_game.c than it does in y_inter.c
+//
+// ...Gee, (why) end the game?
+// Because G_AfterIntermission and F_EndCutscene would
+// both do this exact same thing *in different ways* otherwise,
+// which made it so that you could only unlock Ultimate mode
+// if you had a cutscene after the final level and crap like that.
+// This function simplifies it so only one place has to be updated
+// when something new is added.
+void G_EndGame(void)
+{
+ // Only do evaluation and credits in coop games.
+ if (gametype == GT_COOP)
+ {
+ if (nextmap == 1102-1) // end game with credits
+ {
+ F_StartCredits();
+ return;
+ }
+ if (nextmap == 1101-1) // end game with evaluation
+ {
+ F_StartGameEvaluation();
+ return;
+ }
+ }
+
+ // 1100 or competitive multiplayer, so go back to title screen.
+ D_StartTitle();
+}
+
//
// G_LoadGameSettings
//
@@ -3540,7 +3582,7 @@ void G_DeferedInitNew(boolean pultmode, const char *mapname, INT32 pickedchar, b
// This is the map command interpretation something like Command_Map_f
//
// called at: map cmd execution, doloadgame, doplaydemo
-void G_InitNew(UINT8 pultmode, const char *mapname, boolean resetplayer, boolean skipprecutscene)
+void G_InitNew(UINT8 pultmode, const char *mapname, boolean resetplayer, boolean skipprecutscene, boolean FLS)
{
INT32 i;
@@ -3570,7 +3612,8 @@ void G_InitNew(UINT8 pultmode, const char *mapname, boolean resetplayer, boolean
if (netgame || multiplayer)
{
- players[i].lives = cv_startinglives.value;
+ if (!FLS || (players[i].lives < cv_startinglives.value))
+ players[i].lives = cv_startinglives.value;
players[i].continues = 0;
}
else if (pultmode)
@@ -3584,13 +3627,16 @@ void G_InitNew(UINT8 pultmode, const char *mapname, boolean resetplayer, boolean
players[i].continues = 1;
}
+ if (!((netgame || multiplayer) && (FLS)))
+ players[i].score = 0;
+
// The latter two should clear by themselves, but just in case
players[i].pflags &= ~(PF_TAGIT|PF_TAGGED|PF_FULLSTASIS);
// Clear cheatcodes too, just in case.
players[i].pflags &= ~(PF_GODMODE|PF_NOCLIP|PF_INVIS);
- players[i].score = players[i].xtralife = 0;
+ players[i].xtralife = 0;
}
// Reset unlockable triggers
@@ -3624,7 +3670,6 @@ void G_InitNew(UINT8 pultmode, const char *mapname, boolean resetplayer, boolean
mapmusflags |= MUSIC_RELOADRESET;
ultimatemode = pultmode;
- playerdeadview = false;
automapactive = false;
imcontinuing = false;
@@ -3652,6 +3697,9 @@ char *G_BuildMapTitle(INT32 mapnum)
{
char *title = NULL;
+ if (!mapheaderinfo[mapnum-1])
+ P_AllocMapHeader(mapnum-1);
+
if (strcmp(mapheaderinfo[mapnum-1]->lvlttl, ""))
{
size_t len = 1;
@@ -3885,7 +3933,7 @@ void G_GhostAddColor(ghostcolor_t color)
ghostext.color = (UINT8)color;
}
-void G_GhostAddScale(UINT16 scale)
+void G_GhostAddScale(fixed_t scale)
{
if (!demorecording || !(demoflags & DF_GHOST))
return;
@@ -3919,12 +3967,8 @@ void G_WriteGhostTic(mobj_t *ghost)
if (!(demoflags & DF_GHOST))
return; // No ghost data to write.
- if (ghost->player && ghost->player->pflags & PF_NIGHTSMODE && ghost->tracer)
- {
- // We're talking about the NiGHTS thing, not the normal platforming thing!
+ if (ghost->player && ghost->player->powers[pw_carry] == CR_NIGHTSMODE) // We're talking about the NiGHTS thing, not the normal platforming thing!
ziptic |= GZT_NIGHTS;
- ghost = ghost->tracer;
- }
ziptic_p = demo_p++; // the ziptic, written at the end of this function
@@ -4106,11 +4150,9 @@ void G_ConsGhostTic(void)
demo_p++;
if (ziptic & GZT_SPR2)
demo_p++;
- if(ziptic & GZT_NIGHTS) {
- if (!testmo->player || !(testmo->player->pflags & PF_NIGHTSMODE) || !testmo->tracer)
+ if (ziptic & GZT_NIGHTS) {
+ if (!testmo->player || !(testmo->player->powers[pw_carry] == CR_NIGHTSMODE))
nightsfail = true;
- else
- testmo = testmo->tracer;
}
if (ziptic & GZT_EXTRA)
@@ -4727,7 +4769,7 @@ void G_BeginRecording(void)
// Don't do it.
WRITEFIXED(demo_p, player->jumpfactor);
- // Save netvar data (SONICCD, etc)
+ // Save netvar data
CV_SaveNetVars(&demo_p);
memset(&oldcmd,0,sizeof(oldcmd));
@@ -5141,7 +5183,7 @@ void G_DoPlayDemo(char *defdemoname)
memset(playeringame,0,sizeof(playeringame));
playeringame[0] = true;
P_SetRandSeed(randseed);
- G_InitNew(false, G_BuildMapName(gamemap), true, true);
+ G_InitNew(false, G_BuildMapName(gamemap), true, true, false);
// Set skin
SetPlayerSkin(0, skin);
@@ -5621,7 +5663,7 @@ boolean G_CheckDemoStatus(void)
WRITEUINT8(demo_p, DEMOMARKER); // add the demo end marker
md5_buffer((char *)p+16, demo_p - (p+16), p); // make a checksum of everything after the checksum in the file.
#endif
- saved = FIL_WriteFile(demoname, demobuffer, demo_p - demobuffer); // finally output the file.
+ saved = FIL_WriteFile(va(pandf, srb2home, demoname), demobuffer, demo_p - demobuffer); // finally output the file.
free(demobuffer);
demorecording = false;
diff --git a/src/g_game.h b/src/g_game.h
index 6d4125517..72a6f3d6e 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -56,6 +56,9 @@ extern INT16 rw_maximums[NUM_WEAPONS];
// used in game menu
extern consvar_t cv_crosshair, cv_crosshair2;
extern consvar_t cv_invertmouse, cv_alwaysfreelook, cv_mousemove;
+extern consvar_t cv_invertmouse2, cv_alwaysfreelook2, cv_mousemove2;
+extern consvar_t cv_useranalog, cv_useranalog2;
+extern consvar_t cv_analog, cv_analog2;
extern consvar_t cv_sideaxis,cv_turnaxis,cv_moveaxis,cv_lookaxis,cv_fireaxis,cv_firenaxis;
extern consvar_t cv_sideaxis2,cv_turnaxis2,cv_moveaxis2,cv_lookaxis2,cv_fireaxis2,cv_firenaxis2;
extern consvar_t cv_ghost_bestscore, cv_ghost_besttime, cv_ghost_bestrings, cv_ghost_last, cv_ghost_guest;
@@ -89,7 +92,7 @@ void G_ChangePlayerReferences(mobj_t *oldmo, mobj_t *newmo);
void G_DoReborn(INT32 playernum);
void G_PlayerReborn(INT32 player);
void G_InitNew(UINT8 pultmode, const char *mapname, boolean resetplayer,
- boolean skipprecutscene);
+ boolean skipprecutscene, boolean FLS);
char *G_BuildMapTitle(INT32 mapnum);
// XMOD spawning
@@ -139,7 +142,7 @@ void G_GhostAddSpin(void);
void G_GhostAddRev(void);
void G_GhostAddColor(ghostcolor_t color);
void G_GhostAddFlip(void);
-void G_GhostAddScale(UINT16 scale);
+void G_GhostAddScale(fixed_t scale);
void G_GhostAddHit(mobj_t *victim);
void G_WriteGhostTic(mobj_t *ghost);
void G_ConsGhostTic(void);
@@ -171,6 +174,7 @@ void G_NextLevel(void);
void G_Continue(void);
void G_UseContinue(void);
void G_AfterIntermission(void);
+void G_EndGame(void); // moved from y_inter.c/h and renamed
void G_Ticker(boolean run);
boolean G_Responder(event_t *ev);
diff --git a/src/g_input.c b/src/g_input.c
index b004384c0..36b8373aa 100644
--- a/src/g_input.c
+++ b/src/g_input.c
@@ -25,10 +25,10 @@ static CV_PossibleValue_t mousesens_cons_t[] = {{1, "MIN"}, {MAXMOUSESENSITIVITY
static CV_PossibleValue_t onecontrolperkey_cons_t[] = {{1, "One"}, {2, "Several"}, {0, NULL}};
// mouse values are used once
-consvar_t cv_mousesens = {"mousesens", "35", CV_SAVE, mousesens_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_mousesens2 = {"mousesens2", "35", CV_SAVE, mousesens_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_mouseysens = {"mouseysens", "35", CV_SAVE, mousesens_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_mouseysens2 = {"mouseysens2", "35", CV_SAVE, mousesens_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_mousesens = {"mousesens", "12", CV_SAVE, mousesens_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_mousesens2 = {"mousesens2", "12", CV_SAVE, mousesens_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_mouseysens = {"mouseysens", "12", CV_SAVE, mousesens_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_mouseysens2 = {"mouseysens2", "12", CV_SAVE, mousesens_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
consvar_t cv_controlperkey = {"controlperkey", "One", CV_SAVE, onecontrolperkey_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
INT32 mousex, mousey;
@@ -977,8 +977,6 @@ static const char *gamecontrolname[num_gamecontrols] =
"tossflag",
"use",
"camtoggle",
- "camleft",
- "camright",
"camreset",
"lookup",
"lookdown",
@@ -1074,8 +1072,6 @@ void G_Controldefault(void)
gamecontrol[gc_use ][0] = KEY_JOY1+1; //B
gamecontrol[gc_use ][1] = '.';
gamecontrol[gc_camtoggle ][1] = ',';
- gamecontrol[gc_camleft ][0] = 'o';
- gamecontrol[gc_camright ][0] = 'p';
gamecontrol[gc_camreset ][0] = 'c';
gamecontrol[gc_lookup ][0] = KEY_PGUP;
gamecontrol[gc_lookdown ][0] = KEY_PGDN;
@@ -1154,10 +1150,8 @@ void G_Controldefault(void)
#else
void G_Controldefault(void)
{
- gamecontrol[gc_forward ][0] = KEY_UPARROW;
- gamecontrol[gc_forward ][1] = 'w';
- gamecontrol[gc_backward ][0] = KEY_DOWNARROW;
- gamecontrol[gc_backward ][1] = 's';
+ gamecontrol[gc_forward ][0] = 'w';
+ gamecontrol[gc_backward ][0] = 's';
gamecontrol[gc_strafeleft ][0] = 'a';
gamecontrol[gc_straferight][0] = 'd';
gamecontrol[gc_turnleft ][0] = KEY_LEFTARROW;
@@ -1178,19 +1172,16 @@ void G_Controldefault(void)
gamecontrol[gc_fire ][1] = KEY_MOUSE1+0;
gamecontrol[gc_firenormal ][0] = 'c';
gamecontrol[gc_tossflag ][0] = '\'';
- gamecontrol[gc_use ][0] = 'x';
+ gamecontrol[gc_use ][0] = KEY_LSHIFT;
gamecontrol[gc_camtoggle ][0] = 'v';
- gamecontrol[gc_camleft ][0] = '[';
- gamecontrol[gc_camright ][0] = ']';
gamecontrol[gc_camreset ][0] = 'r';
- gamecontrol[gc_lookup ][0] = KEY_PGUP;
- gamecontrol[gc_lookdown ][0] = KEY_PGDN;
+ gamecontrol[gc_lookup ][0] = KEY_UPARROW;
+ gamecontrol[gc_lookdown ][0] = KEY_DOWNARROW;
gamecontrol[gc_centerview ][0] = KEY_END;
gamecontrol[gc_talkkey ][0] = 't';
gamecontrol[gc_teamkey ][0] = 'y';
gamecontrol[gc_scores ][0] = KEY_TAB;
- gamecontrol[gc_jump ][0] = 'z';
- gamecontrol[gc_jump ][1] = KEY_MOUSE1+1;
+ gamecontrol[gc_jump ][0] = KEY_SPACE;
gamecontrol[gc_console ][0] = KEY_CONSOLE;
gamecontrol[gc_pause ][0] = KEY_PAUSE;
#ifdef WMINPUT
diff --git a/src/g_input.h b/src/g_input.h
index d65339321..808397438 100644
--- a/src/g_input.h
+++ b/src/g_input.h
@@ -105,8 +105,6 @@ typedef enum
gc_tossflag,
gc_use,
gc_camtoggle,
- gc_camleft,
- gc_camright,
gc_camreset,
gc_lookup,
gc_lookdown,
@@ -126,6 +124,8 @@ typedef enum
// mouse values are used once
extern consvar_t cv_mousesens, cv_mouseysens;
+extern consvar_t cv_mousesens2, cv_mouseysens2;
+extern consvar_t cv_controlperkey;
extern INT32 mousex, mousey;
extern INT32 mlooky; //mousey with mlookSensitivity
diff --git a/src/hardware/hw_bsp.c b/src/hardware/hw_bsp.c
index 17eb8761c..a32609fc8 100644
--- a/src/hardware/hw_bsp.c
+++ b/src/hardware/hw_bsp.c
@@ -564,8 +564,6 @@ static inline void HWR_SubsecPoly(INT32 num, poly_t *poly)
subsector_t *sub;
seg_t *lseg;
- sscount++;
-
sub = &subsectors[num];
count = sub->numlines;
lseg = &segs[sub->firstline];
diff --git a/src/hardware/hw_draw.c b/src/hardware/hw_draw.c
index 60183b58e..9c912495a 100644
--- a/src/hardware/hw_draw.c
+++ b/src/hardware/hw_draw.c
@@ -152,7 +152,9 @@ void HWR_DrawFixedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscale,
float pdupx = FIXED_TO_FLOAT(vid.fdupx)*2.0f*FIXED_TO_FLOAT(pscale);
float pdupy = FIXED_TO_FLOAT(vid.fdupy)*2.0f*FIXED_TO_FLOAT(pscale);
- if (alphalevel >= 10 && alphalevel < 13)
+ if (alphalevel == 12)
+ alphalevel = 0;
+ else if (alphalevel >= 10 && alphalevel < 13)
return;
// make patch ready in hardware cache
@@ -252,7 +254,9 @@ void HWR_DrawCroppedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscal
float pdupx = FIXED_TO_FLOAT(vid.fdupx)*2.0f*FIXED_TO_FLOAT(pscale);
float pdupy = FIXED_TO_FLOAT(vid.fdupy)*2.0f*FIXED_TO_FLOAT(pscale);
- if (alphalevel >= 10 && alphalevel < 13)
+ if (alphalevel == 12)
+ alphalevel = 0;
+ else if (alphalevel >= 10 && alphalevel < 13)
return;
// make patch ready in hardware cache
@@ -656,6 +660,7 @@ void HWR_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 color)
{
FOutVector v[4];
FSurfaceInfo Surf;
+ float sdupx, sdupy;
if (w < 0 || h < 0)
return; // consistency w/ software
@@ -664,10 +669,16 @@ void HWR_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 color)
// | /|
// |/ |
// 0--1
- v[0].x = v[3].x = (x - 160.0f)/160.0f;
- v[2].x = v[1].x = ((x+w) - 160.0f)/160.0f;
- v[0].y = v[1].y = -(y - 100.0f)/100.0f;
- v[2].y = v[3].y = -((y+h) - 100.0f)/100.0f;
+ sdupx = FIXED_TO_FLOAT(vid.fdupx)*2.0f;
+ sdupy = FIXED_TO_FLOAT(vid.fdupy)*2.0f;
+
+ if (color & V_NOSCALESTART)
+ sdupx = sdupy = 2.0f;
+
+ v[0].x = v[3].x = (x*sdupx)/vid.width - 1;
+ v[2].x = v[1].x = (x*sdupx + w*sdupx)/vid.width - 1;
+ v[0].y = v[1].y = 1-(y*sdupy)/vid.height;
+ v[2].y = v[3].y = 1-(y*sdupy + h*sdupy)/vid.height;
//Hurdler: do we still use this argb color? if not, we should remove it
v[0].argb = v[1].argb = v[2].argb = v[3].argb = 0xff00ff00; //;
@@ -778,7 +789,7 @@ boolean HWR_Screenshot(const char *lbmname)
HWD.pfnReadRect(0, 0, vid.width, vid.height, vid.width * 3, (void *)buf);
#ifdef USE_PNG
- ret = M_SavePNG(lbmname, buf, vid.width, vid.height, NULL);
+ ret = M_SavePNG(lbmname, buf, vid.width, vid.height, false);
#else
ret = saveTGA(lbmname, buf, vid.width, vid.height);
#endif
diff --git a/src/hardware/hw_glob.h b/src/hardware/hw_glob.h
index 94eef1d3e..5d1a81d4f 100644
--- a/src/hardware/hw_glob.h
+++ b/src/hardware/hw_glob.h
@@ -78,6 +78,7 @@ typedef struct gr_vissprite_s
//Hurdler: 25/04/2000: now support colormap in hardware mode
UINT8 *colormap;
INT32 dispoffset; // copy of info->dispoffset, affects ordering but not drawing
+ float z1, z2;
} gr_vissprite_t;
// --------
diff --git a/src/hardware/hw_light.c b/src/hardware/hw_light.c
index 7dfc6f624..a49a788e6 100644
--- a/src/hardware/hw_light.c
+++ b/src/hardware/hw_light.c
@@ -247,27 +247,33 @@ light_t *t_lspr[NUMSPRITES] =
&lspr[NOLIGHT], // SPR_BMNE
// Monitor Boxes
- &lspr[NOLIGHT], // SPR_SRBX
- &lspr[NOLIGHT], // SPR_RRBX
- &lspr[NOLIGHT], // SPR_BRBX
- &lspr[NOLIGHT], // SPR_SHTV
- &lspr[NOLIGHT], // SPR_PINV
- &lspr[NOLIGHT], // SPR_YLTV
- &lspr[NOLIGHT], // SPR_BLTV
- &lspr[NOLIGHT], // SPR_BKTV
- &lspr[NOLIGHT], // SPR_WHTV
- &lspr[NOLIGHT], // SPR_GRTV
- &lspr[NOLIGHT], // SPR_ELTV
- &lspr[NOLIGHT], // SPR_EGGB
- &lspr[NOLIGHT], // SPR_MIXU
- &lspr[NOLIGHT], // SPR_RECY
- &lspr[NOLIGHT], // SPR_QUES
- &lspr[NOLIGHT], // SPR_GBTV
- &lspr[NOLIGHT], // SPR_PRUP
- &lspr[NOLIGHT], // SPR_PTTV
+ &lspr[NOLIGHT], // SPR_MSTV
+ &lspr[NOLIGHT], // SPR_XLTV
- // Monitor Miscellany
- &lspr[NOLIGHT], // SPR_MTEX
+ &lspr[NOLIGHT], // SPR_TRRI
+ &lspr[NOLIGHT], // SPR_TBRI
+
+ &lspr[NOLIGHT], // SPR_TVRI
+ &lspr[NOLIGHT], // SPR_TVPI
+ &lspr[NOLIGHT], // SPR_TVAT
+ &lspr[NOLIGHT], // SPR_TVFO
+ &lspr[NOLIGHT], // SPR_TVAR
+ &lspr[NOLIGHT], // SPR_TVWW
+ &lspr[NOLIGHT], // SPR_TVEL
+ &lspr[NOLIGHT], // SPR_TVSS
+ &lspr[NOLIGHT], // SPR_TVIV
+ &lspr[NOLIGHT], // SPR_TV1U
+ &lspr[NOLIGHT], // SPR_TV1P
+ &lspr[NOLIGHT], // SPR_TVEG
+ &lspr[NOLIGHT], // SPR_TVMX
+ &lspr[NOLIGHT], // SPR_TVMY
+ &lspr[NOLIGHT], // SPR_TVGV
+ &lspr[NOLIGHT], // SPR_TVRC
+ &lspr[NOLIGHT], // SPR_TV1K
+ &lspr[NOLIGHT], // SPR_TVTK
+ &lspr[NOLIGHT], // SPR_TVFL
+ &lspr[NOLIGHT], // SPR_TVBB
+ &lspr[NOLIGHT], // SPR_TVZP
// Projectiles
&lspr[NOLIGHT], // SPR_MISL
@@ -290,6 +296,7 @@ light_t *t_lspr[NUMSPRITES] =
// Techno Hill Scenery
&lspr[NOLIGHT], // SPR_THZP
+ &lspr[NOLIGHT], // SPR_FWR5
&lspr[REDBALL_L], // SPR_ALRM
// Deep Sea Scenery
@@ -356,18 +363,32 @@ light_t *t_lspr[NUMSPRITES] =
&lspr[NOLIGHT], // SPR_ELEM
&lspr[NOLIGHT], // SPR_FORC
&lspr[NOLIGHT], // SPR_PITY
+ &lspr[NOLIGHT], // SPR_FIRS
+ &lspr[NOLIGHT], // SPR_BUBS
+ &lspr[NOLIGHT], // SPR_ZAPS
&lspr[INVINCIBLE_L], // SPR_IVSP
&lspr[SUPERSPARK_L], // SPR_SSPK
&lspr[NOLIGHT], // SPR_GOAL
- // Freed Animals
- &lspr[NOLIGHT], // SPR_BIRD
- &lspr[NOLIGHT], // SPR_BUNY
- &lspr[NOLIGHT], // SPR_MOUS
- &lspr[NOLIGHT], // SPR_CHIC
- &lspr[NOLIGHT], // SPR_COWZ
- &lspr[NOLIGHT], // SPR_RBRD
+ // Flickies
+ &lspr[NOLIGHT], // SPR_FBUB
+ &lspr[NOLIGHT], // SPR_FL01
+ &lspr[NOLIGHT], // SPR_FL02
+ &lspr[NOLIGHT], // SPR_FL03
+ &lspr[NOLIGHT], // SPR_FL04
+ &lspr[NOLIGHT], // SPR_FL05
+ &lspr[NOLIGHT], // SPR_FL06
+ &lspr[NOLIGHT], // SPR_FL07
+ &lspr[NOLIGHT], // SPR_FL08
+ &lspr[NOLIGHT], // SPR_FL09
+ &lspr[NOLIGHT], // SPR_FL10
+ &lspr[NOLIGHT], // SPR_FL11
+ &lspr[NOLIGHT], // SPR_FL12
+ &lspr[NOLIGHT], // SPR_FL13
+ &lspr[NOLIGHT], // SPR_FL14
+ &lspr[NOLIGHT], // SPR_FL15
+ &lspr[NOLIGHT], // SPR_FL16
// Springs
&lspr[NOLIGHT], // SPR_SPRY
@@ -383,7 +404,7 @@ light_t *t_lspr[NUMSPRITES] =
&lspr[NOLIGHT], // SPR_SPLA
&lspr[NOLIGHT], // SPR_SMOK
&lspr[NOLIGHT], // SPR_BUBL
- &lspr[SUPERSPARK_L], // SPR_WZAP
+ &lspr[RINGLIGHT_L], // SPR_WZAP
&lspr[SUPERSPARK_L], // SPR_TFOG
&lspr[NIGHTSLIGHT_L], // SPR_SEED // Sonic CD flower seed
&lspr[NOLIGHT], // SPR_PRTL
@@ -391,9 +412,12 @@ light_t *t_lspr[NUMSPRITES] =
// Game Indicators
&lspr[NOLIGHT], // SPR_SCOR
&lspr[NOLIGHT], // SPR_DRWN
+ &lspr[NOLIGHT], // SPR_LCKN
&lspr[NOLIGHT], // SPR_TTAG
&lspr[NOLIGHT], // SPR_GFLG
+ &lspr[NOLIGHT], // SPR_CORK
+
// Ring Weapons
&lspr[RINGLIGHT_L], // SPR_RRNG
&lspr[RINGLIGHT_L], // SPR_RNGB
@@ -473,23 +497,6 @@ light_t *t_lspr[NUMSPRITES] =
&lspr[NOLIGHT], // SPR_GWLG
&lspr[NOLIGHT], // SPR_GWLR
- // SRB1 Sprites
- &lspr[NOLIGHT], // SPR_SRBA
- &lspr[NOLIGHT], // SPR_SRBB
- &lspr[NOLIGHT], // SPR_SRBC
- &lspr[NOLIGHT], // SPR_SRBD
- &lspr[NOLIGHT], // SPR_SRBE
- &lspr[NOLIGHT], // SPR_SRBF
- &lspr[NOLIGHT], // SPR_SRBG
- &lspr[NOLIGHT], // SPR_SRBH
- &lspr[NOLIGHT], // SPR_SRBI
- &lspr[NOLIGHT], // SPR_SRBJ
- &lspr[NOLIGHT], // SPR_SRBK
- &lspr[NOLIGHT], // SPR_SRBL
- &lspr[NOLIGHT], // SPR_SRBM
- &lspr[NOLIGHT], // SPR_SRBN
- &lspr[NOLIGHT], // SPR_SRBO
-
// Free slots
&lspr[NOLIGHT],
&lspr[NOLIGHT],
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index e69a74558..92c363943 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -45,7 +45,7 @@
#include "hw_md2.h"
#define R_FAKEFLOORS
-//#define HWPRECIP
+#define HWPRECIP
#define SORTING
//#define POLYSKY
@@ -1084,9 +1084,9 @@ static void HWR_SplitWall(sector_t *sector, wallVert3D *wallVerts, INT32 texnum,
float endheight = 0.0f, endbheight = 0.0f;
fixed_t v1x = FLOAT_TO_FIXED(wallVerts[0].x);
- fixed_t v1y = FLOAT_TO_FIXED(wallVerts[0].y);
+ fixed_t v1y = FLOAT_TO_FIXED(wallVerts[0].z); // not a typo
fixed_t v2x = FLOAT_TO_FIXED(wallVerts[1].x);
- fixed_t v2y = FLOAT_TO_FIXED(wallVerts[1].y);
+ fixed_t v2y = FLOAT_TO_FIXED(wallVerts[1].z); // not a typo
// compiler complains when P_GetZAt is used in FLOAT_TO_FIXED directly
// use this as a temp var to store P_GetZAt's return value each time
fixed_t temp;
@@ -1558,6 +1558,7 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
if (gr_backsector)
{
+ INT32 gr_toptexture, gr_bottomtexture;
// two sided line
if (gr_backsector->heightsec != -1)
{
@@ -1608,19 +1609,22 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
#endif
}
+ gr_toptexture = R_GetTextureNum(gr_sidedef->toptexture);
+ gr_bottomtexture = R_GetTextureNum(gr_sidedef->bottomtexture);
+
// check TOP TEXTURE
if ((
#ifdef ESLOPE
worldhighslope < worldtopslope ||
#endif
worldhigh < worldtop
- ) && texturetranslation[gr_sidedef->toptexture])
+ ) && gr_toptexture)
{
if (drawtextured)
{
fixed_t texturevpegtop; // top
- grTex = HWR_GetTexture(texturetranslation[gr_sidedef->toptexture]);
+ grTex = HWR_GetTexture(gr_toptexture);
// PEGGING
if (gr_linedef->flags & ML_DONTPEGTOP)
@@ -1638,7 +1642,7 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
texturevpegtop += gr_sidedef->rowoffset;
// This is so that it doesn't overflow and screw up the wall, it doesn't need to go higher than the texture's height anyway
- texturevpegtop %= SHORT(textures[texturetranslation[gr_sidedef->toptexture]]->height)<height)<scaleY;
wallVerts[0].t = wallVerts[1].t = (texturevpegtop + gr_frontsector->ceilingheight - gr_backsector->ceilingheight) * grTex->scaleY;
@@ -1683,9 +1687,9 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
#endif
if (gr_frontsector->numlights)
- HWR_SplitWall(gr_frontsector, wallVerts, texturetranslation[gr_sidedef->toptexture], &Surf, FF_CUTSOLIDS);
+ HWR_SplitWall(gr_frontsector, wallVerts, gr_toptexture, &Surf, FF_CUTSOLIDS);
else if (grTex->mipmap.flags & TF_TRANSPARENT)
- HWR_AddTransparentWall(wallVerts, &Surf, texturetranslation[gr_sidedef->toptexture], PF_Environment, false, lightnum, colormap);
+ HWR_AddTransparentWall(wallVerts, &Surf, gr_toptexture, PF_Environment, false, lightnum, colormap);
else
HWR_ProjectWall(wallVerts, &Surf, PF_Masked, lightnum, colormap);
}
@@ -1695,13 +1699,13 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
#ifdef ESLOPE
worldlowslope > worldbottomslope ||
#endif
- worldlow > worldbottom) && texturetranslation[gr_sidedef->bottomtexture]) //only if VISIBLE!!!
+ worldlow > worldbottom) && gr_bottomtexture) //only if VISIBLE!!!
{
if (drawtextured)
{
fixed_t texturevpegbottom = 0; // bottom
- grTex = HWR_GetTexture(texturetranslation[gr_sidedef->bottomtexture]);
+ grTex = HWR_GetTexture(gr_bottomtexture);
// PEGGING
#ifdef ESLOPE
@@ -1721,7 +1725,7 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
texturevpegbottom += gr_sidedef->rowoffset;
// This is so that it doesn't overflow and screw up the wall, it doesn't need to go higher than the texture's height anyway
- texturevpegbottom %= SHORT(textures[texturetranslation[gr_sidedef->bottomtexture]]->height)<height)<scaleY;
wallVerts[0].t = wallVerts[1].t = (texturevpegbottom + gr_backsector->floorheight - gr_frontsector->floorheight) * grTex->scaleY;
@@ -1766,13 +1770,13 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
#endif
if (gr_frontsector->numlights)
- HWR_SplitWall(gr_frontsector, wallVerts, texturetranslation[gr_sidedef->bottomtexture], &Surf, FF_CUTSOLIDS);
+ HWR_SplitWall(gr_frontsector, wallVerts, gr_bottomtexture, &Surf, FF_CUTSOLIDS);
else if (grTex->mipmap.flags & TF_TRANSPARENT)
- HWR_AddTransparentWall(wallVerts, &Surf, texturetranslation[gr_sidedef->bottomtexture], PF_Environment, false, lightnum, colormap);
+ HWR_AddTransparentWall(wallVerts, &Surf, gr_bottomtexture, PF_Environment, false, lightnum, colormap);
else
HWR_ProjectWall(wallVerts, &Surf, PF_Masked, lightnum, colormap);
}
- gr_midtexture = texturetranslation[gr_sidedef->midtexture];
+ gr_midtexture = R_GetTextureNum(gr_sidedef->midtexture);
if (gr_midtexture)
{
FBITFIELD blendmode;
@@ -2134,7 +2138,7 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
else
{
// Single sided line... Deal only with the middletexture (if one exists)
- gr_midtexture = texturetranslation[gr_sidedef->midtexture];
+ gr_midtexture = R_GetTextureNum(gr_sidedef->midtexture);
if (gr_midtexture)
{
if (drawtextured)
@@ -2232,13 +2236,13 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
if (*rover->topheight < lowcut || *rover->bottomheight > highcut)
continue;
- texnum = texturetranslation[sides[rover->master->sidenum[0]].midtexture];
+ texnum = R_GetTextureNum(sides[rover->master->sidenum[0]].midtexture);
if (rover->master->flags & ML_TFERLINE)
{
size_t linenum = gr_curline->linedef-gr_backsector->lines[0];
newline = rover->master->frontsector->lines[0] + linenum;
- texnum = texturetranslation[sides[newline->sidenum[0]].midtexture];
+ texnum = R_GetTextureNum(sides[newline->sidenum[0]].midtexture);
}
#ifdef ESLOPE
@@ -2366,13 +2370,13 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
if (*rover->topheight < lowcut || *rover->bottomheight > highcut)
continue;
- texnum = texturetranslation[sides[rover->master->sidenum[0]].midtexture];
+ texnum = R_GetTextureNum(sides[rover->master->sidenum[0]].midtexture);
if (rover->master->flags & ML_TFERLINE)
{
size_t linenum = gr_curline->linedef-gr_backsector->lines[0];
newline = rover->master->frontsector->lines[0] + linenum;
- texnum = texturetranslation[sides[newline->sidenum[0]].midtexture];
+ texnum = R_GetTextureNum(sides[newline->sidenum[0]].midtexture);
}
#ifdef ESLOPE //backsides
h = *rover->t_slope ? P_GetZAt(*rover->t_slope, v1x, v1y) : *rover->topheight;
@@ -3363,7 +3367,6 @@ static void HWR_Subsector(size_t num)
if (num < numsubsectors)
{
- sscount++;
// subsector
sub = &subsectors[num];
// sector
@@ -3718,6 +3721,9 @@ static void HWR_Subsector(size_t num)
while (count--)
{
+#ifdef POLYOBJECTS
+ if (!line->polyseg) // ignore segs that belong to polyobjects
+#endif
HWR_AddLine(line);
line++;
}
@@ -4223,6 +4229,7 @@ static void HWR_DrawSprite(gr_vissprite_t *spr)
GLPatch_t *gpatch; // sprite patch converted to hardware
FSurfaceInfo Surf;
const boolean hires = (spr->mobj && spr->mobj->skin && ((skin_t *)spr->mobj->skin)->flags & SF_HIRES);
+ //const boolean papersprite = (spr->mobj && (spr->mobj->frame & FF_PAPERSPRITE));
if (spr->mobj)
this_scale = FIXED_TO_FLOAT(spr->mobj->scale);
if (hires)
@@ -4266,7 +4273,8 @@ static void HWR_DrawSprite(gr_vissprite_t *spr)
// make a wall polygon (with 2 triangles), using the floor/ceiling heights,
// and the 2d map coords of start/end vertices
- wallVerts[0].z = wallVerts[1].z = wallVerts[2].z = wallVerts[3].z = spr->tz;
+ wallVerts[0].z = wallVerts[3].z = spr->z1;
+ wallVerts[2].z = wallVerts[1].z = spr->z2;
// transform
wv = wallVerts;
@@ -4401,7 +4409,6 @@ static inline void HWR_DrawPrecipitationSprite(gr_vissprite_t *spr)
FOutVector *wv;
GLPatch_t *gpatch; // sprite patch converted to hardware
FSurfaceInfo Surf;
- sector_t *sector;
if (!spr->mobj)
return;
@@ -4455,19 +4462,38 @@ static inline void HWR_DrawPrecipitationSprite(gr_vissprite_t *spr)
//Hurdler: 25/04/2000: now support colormap in hardware mode
HWR_GetMappedPatch(gpatch, spr->colormap);
- sector = spr->mobj->subsector->sector;
-
- if (sector->ffloors)
+ // colormap test
{
- ffloor_t *caster = sector->lightlist[R_GetPlaneLight(sector, spr->mobj->z, false)].caster;
- sector = caster ? §ors[caster->secnum] : sector;
- }
+ sector_t *sector = spr->mobj->subsector->sector;
+ UINT8 lightlevel = 255;
+ extracolormap_t *colormap = sector->extra_colormap;
- // sprite lighting by modulating the RGB components
- if (sector->extra_colormap)
- Surf.FlatColor.rgba = HWR_Lighting(spr->sectorlight,sector->extra_colormap->rgba,sector->extra_colormap->fadergba, false, false);
+ if (sector->numlights)
+ {
+ INT32 light;
+
+ light = R_GetPlaneLight(sector, spr->mobj->z + spr->mobj->height, false); // Always use the light at the top instead of whatever I was doing before
+
+ if (!(spr->mobj->frame & FF_FULLBRIGHT))
+ lightlevel = *sector->lightlist[light].lightlevel;
+
+ if (sector->lightlist[light].extra_colormap)
+ colormap = sector->lightlist[light].extra_colormap;
+ }
else
- Surf.FlatColor.rgba = HWR_Lighting(spr->sectorlight,NORMALFOG,FADEFOG, false, false);
+ {
+ if (!(spr->mobj->frame & FF_FULLBRIGHT))
+ lightlevel = sector->lightlevel;
+
+ if (sector->extra_colormap)
+ colormap = sector->extra_colormap;
+ }
+
+ if (colormap)
+ Surf.FlatColor.rgba = HWR_Lighting(lightlevel, colormap->rgba, colormap->fadergba, false, false);
+ else
+ Surf.FlatColor.rgba = HWR_Lighting(lightlevel, NORMALFOG, FADEFOG, false, false);
+ }
if (spr->mobj->flags2 & MF2_SHADOW)
{
@@ -4501,8 +4527,8 @@ static void HWR_SortVisSprites(void)
gr_vissprite_t *ds, *dsprev, *dsnext, *dsfirst;
gr_vissprite_t *best = NULL;
gr_vissprite_t unsorted;
- float bestdist;
- INT32 bestdispoffset;
+ float bestdist = 0.0f;
+ INT32 bestdispoffset = 0;
if (!gr_visspritecount)
return;
@@ -5040,8 +5066,14 @@ static void HWR_ProjectSprite(mobj_t *thing)
size_t lumpoff;
unsigned rot;
UINT8 flip;
+ boolean vflip = (!(thing->eflags & MFE_VERTICALFLIP) != !(thing->frame & FF_VERTICALFLIP));
+
angle_t ang;
INT32 heightsec, phs;
+ const boolean papersprite = (thing->frame & FF_PAPERSPRITE);
+ float offset;
+ float ang_scale = 1.0f, ang_scalez = 0.0f;
+ float z1, z2;
if (!thing)
return;
@@ -5056,7 +5088,7 @@ static void HWR_ProjectSprite(mobj_t *thing)
tz = (tr_x * gr_viewcos) + (tr_y * gr_viewsin);
// thing is behind view plane?
- if (tz < ZCLIP_PLANE && (!cv_grmd2.value || md2_models[thing->sprite].notfound == true)) //Yellow: Only MD2's dont disappear
+ if (tz < ZCLIP_PLANE && !papersprite && (!cv_grmd2.value || md2_models[thing->sprite].notfound == true)) //Yellow: Only MD2's dont disappear
return;
tx = (tr_x * gr_viewsin) - (tr_y * gr_viewcos);
@@ -5094,6 +5126,27 @@ static void HWR_ProjectSprite(mobj_t *thing)
I_Error("sprframes NULL for sprite %d\n", thing->sprite);
#endif
+ if (papersprite)
+ {
+ // Use the actual view angle, rather than the angle formed
+ // between the view point and the thing
+ // this makes sure paper sprites always appear at the right angle!
+ // Note: DO NOT do this in software mode version, it actually
+ // makes papersprites look WORSE there (I know, I've tried)
+ // Monster Iestyn - 13/05/17
+ ang = dup_viewangle - thing->angle;
+ ang_scale = FIXED_TO_FLOAT(FINESINE(ang>>ANGLETOFINESHIFT));
+ ang_scalez = FIXED_TO_FLOAT(FINECOSINE(ang>>ANGLETOFINESHIFT));
+
+ if (ang_scale < 0)
+ {
+ ang_scale = -ang_scale;
+ ang_scalez = -ang_scalez;
+ }
+ }
+ else if (sprframe->rotate != SRF_SINGLE)
+ ang = R_PointToAngle (thing->x, thing->y) - thing->angle;
+
if (sprframe->rotate == SRF_SINGLE)
{
// use single rotation for all views
@@ -5104,8 +5157,6 @@ static void HWR_ProjectSprite(mobj_t *thing)
else
{
// choose a different rotation based on player view
- ang = R_PointToAngle (thing->x, thing->y) - thing->angle;
-
if ((sprframe->rotate & SRF_RIGHT) && (ang < ANGLE_180)) // See from right
rot = 6; // F7 slot
else if ((sprframe->rotate & SRF_LEFT) && (ang >= ANGLE_180)) // See from left
@@ -5123,9 +5174,12 @@ static void HWR_ProjectSprite(mobj_t *thing)
// calculate edges of the shape
if (flip)
- tx -= FIXED_TO_FLOAT(spritecachedinfo[lumpoff].width - spritecachedinfo[lumpoff].offset) * this_scale;
+ offset = FIXED_TO_FLOAT(spritecachedinfo[lumpoff].width - spritecachedinfo[lumpoff].offset) * this_scale;
else
- tx -= FIXED_TO_FLOAT(spritecachedinfo[lumpoff].offset) * this_scale;
+ offset = FIXED_TO_FLOAT(spritecachedinfo[lumpoff].offset) * this_scale;
+
+ z1 = tz - (offset * ang_scalez);
+ tx -= offset * ang_scale;
// project x
x1 = gr_windowcenterx + (tx * gr_centerx / tz);
@@ -5136,10 +5190,17 @@ static void HWR_ProjectSprite(mobj_t *thing)
x1 = tx;
- tx += FIXED_TO_FLOAT(spritecachedinfo[lumpoff].width) * this_scale;
+ offset = FIXED_TO_FLOAT(spritecachedinfo[lumpoff].width) * this_scale;
+
+ z2 = z1 + (offset * ang_scalez);
+ tx += offset * ang_scale;
+
+ if (papersprite && max(z1, z2) < ZCLIP_PLANE)
+ return;
+
x2 = gr_windowcenterx + (tx * gr_centerx / tz);
- if (thing->eflags & MFE_VERTICALFLIP)
+ if (vflip)
{
gz = FIXED_TO_FLOAT(thing->z+thing->height) - FIXED_TO_FLOAT(spritecachedinfo[lumpoff].topoffset) * this_scale;
gzt = gz + FIXED_TO_FLOAT(spritecachedinfo[lumpoff].height) * this_scale;
@@ -5185,6 +5246,8 @@ static void HWR_ProjectSprite(mobj_t *thing)
vis->patchlumpnum = sprframe->lumppat[rot];
vis->flip = flip;
vis->mobj = thing;
+ vis->z1 = z1;
+ vis->z2 = z2;
//Hurdler: 25/04/2000: now support colormap in hardware mode
if ((vis->mobj->flags & MF_BOSS) && (vis->mobj->flags2 & MF2_FRET) && (leveltime & 1)) // Bosses "flash"
@@ -5216,10 +5279,7 @@ static void HWR_ProjectSprite(mobj_t *thing)
//CONS_Debug(DBG_RENDER, "------------------\nH: sprite : %d\nH: frame : %x\nH: type : %d\nH: sname : %s\n\n",
// thing->sprite, thing->frame, thing->type, sprnames[thing->sprite]);
- if (thing->eflags & MFE_VERTICALFLIP)
- vis->vflip = true;
- else
- vis->vflip = false;
+ vis->vflip = vflip;
vis->precip = false;
}
@@ -5296,6 +5356,11 @@ static void HWR_ProjectPrecipitationSprite(precipmobj_t *thing)
//
vis = HWR_NewVisSprite();
vis->x1 = x1;
+#if 0
+ vis->x2 = x2;
+#else
+ (void)x2;
+#endif
vis->x2 = tx;
vis->tz = tz;
vis->dispoffset = 0; // Monster Iestyn: 23/11/15: HARDWARE SUPPORT AT LAST
@@ -5308,7 +5373,6 @@ static void HWR_ProjectPrecipitationSprite(precipmobj_t *thing)
// set top/bottom coords
vis->ty = FIXED_TO_FLOAT(thing->z + spritecachedinfo[lumpoff].topoffset) - gr_viewz;
- vis->sectorlight = 0xff;
vis->precip = true;
}
#endif
@@ -5330,7 +5394,7 @@ static void HWR_DrawSkyBackground(player_t *player)
// 0--1
(void)player;
- HWR_GetTexture(skytexture);
+ HWR_GetTexture(texturetranslation[skytexture]);
//Hurdler: the sky is the only texture who need 4.0f instead of 1.0
// because it's called just after clearing the screen
@@ -5350,7 +5414,7 @@ static void HWR_DrawSkyBackground(player_t *player)
angle = (dup_viewangle + gr_xtoviewangle[0]);
- dimensionmultiply = ((float)textures[skytexture]->width/256.0f);
+ dimensionmultiply = ((float)textures[texturetranslation[skytexture]]->width/256.0f);
v[0].sow = v[3].sow = ((float) angle / ((ANGLE_90-1)*dimensionmultiply));
v[2].sow = v[1].sow = (-1.0f/dimensionmultiply)+((float) angle / ((ANGLE_90-1)*dimensionmultiply));
@@ -5359,7 +5423,7 @@ static void HWR_DrawSkyBackground(player_t *player)
angle = aimingangle;
aspectratio = (float)vid.width/(float)vid.height;
- dimensionmultiply = ((float)textures[skytexture]->height/(128.0f*aspectratio));
+ dimensionmultiply = ((float)textures[texturetranslation[skytexture]]->height/(128.0f*aspectratio));
angleturn = (((float)ANGLE_45-1.0f)*aspectratio)*dimensionmultiply;
// Middle of the sky should always be at angle 0
diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c
index fa4699810..7edf02db0 100644
--- a/src/hardware/hw_md2.c
+++ b/src/hardware/hw_md2.c
@@ -308,6 +308,23 @@ static md2_model_t *md2_readModel(const char *filename)
model->header.numSkins = 1;
+#define MD2LIMITCHECK(field, max, msgname) \
+ if (field > max) \
+ { \
+ CONS_Alert(CONS_ERROR, "md2_readModel: %s has too many " msgname " (# found: %d, maximum: %d)\n", filename, field, max); \
+ md2_freeModel (model); \
+ return 0; \
+ }
+
+ // Uncomment if these are actually needed
+// MD2LIMITCHECK(model->header.numSkins, MD2_MAX_SKINS, "skins")
+// MD2LIMITCHECK(model->header.numTexCoords, MD2_MAX_TEXCOORDS, "texture coordinates")
+ MD2LIMITCHECK(model->header.numTriangles, MD2_MAX_TRIANGLES, "triangles")
+ MD2LIMITCHECK(model->header.numFrames, MD2_MAX_FRAMES, "frames")
+ MD2LIMITCHECK(model->header.numVertices, MD2_MAX_VERTICES, "vertices")
+
+#undef MD2LIMITCHECK
+
// read skins
fseek(file, model->header.offsetSkins, SEEK_SET);
if (model->header.numSkins > 0)
@@ -319,8 +336,6 @@ static md2_model_t *md2_readModel(const char *filename)
md2_freeModel (model);
return 0;
}
-
- ;
}
// read texture coordinates
@@ -334,8 +349,6 @@ static md2_model_t *md2_readModel(const char *filename)
md2_freeModel (model);
return 0;
}
-
-
}
// read triangles
@@ -769,6 +782,7 @@ void HWR_InitMD2(void)
md2_playermodels[s].grpatch = NULL;
md2_playermodels[s].skin = -1;
md2_playermodels[s].notfound = true;
+ md2_playermodels[s].error = false;
}
for (i = 0; i < NUMSPRITES; i++)
{
@@ -777,6 +791,7 @@ void HWR_InitMD2(void)
md2_models[i].grpatch = NULL;
md2_models[i].skin = -1;
md2_models[i].notfound = true;
+ md2_models[i].error = false;
}
// read the md2.dat file
@@ -1349,7 +1364,7 @@ void HWR_DrawMD2(gr_vissprite_t *spr)
UINT32 durs = spr->mobj->state->tics;
UINT32 tics = spr->mobj->tics;
md2_frame_t *curr, *next = NULL;
- const UINT8 flip = (UINT8)((spr->mobj->eflags & MFE_VERTICALFLIP) == MFE_VERTICALFLIP);
+ const UINT8 flip = (UINT8)(!(spr->mobj->eflags & MFE_VERTICALFLIP) != !(spr->mobj->frame & FF_VERTICALFLIP));
spritedef_t *sprdef;
spriteframe_t *sprframe;
float finalscale;
@@ -1378,6 +1393,8 @@ void HWR_DrawMD2(gr_vissprite_t *spr)
else
md2 = &md2_models[spr->mobj->sprite];
+ if (md2->error)
+ return; // we already failed loading this before :(
if (!md2->model)
{
//CONS_Debug(DBG_RENDER, "Loading MD2... (%s)", sprnames[spr->mobj->sprite]);
@@ -1391,6 +1408,7 @@ void HWR_DrawMD2(gr_vissprite_t *spr)
else
{
//CONS_Debug(DBG_RENDER, " FAILED\n");
+ md2->error = true; // prevent endless fail
return;
}
}
@@ -1464,7 +1482,7 @@ void HWR_DrawMD2(gr_vissprite_t *spr)
p.x = FIXED_TO_FLOAT(spr->mobj->x);
p.y = FIXED_TO_FLOAT(spr->mobj->y)+md2->offset;
- if (spr->mobj->eflags & MFE_VERTICALFLIP)
+ if (flip)
p.z = FIXED_TO_FLOAT(spr->mobj->z + spr->mobj->height);
else
p.z = FIXED_TO_FLOAT(spr->mobj->z);
diff --git a/src/hardware/hw_md2.h b/src/hardware/hw_md2.h
index 36078268b..5a7e6d2b3 100644
--- a/src/hardware/hw_md2.h
+++ b/src/hardware/hw_md2.h
@@ -123,6 +123,7 @@ typedef struct
void *blendgrpatch;
boolean notfound;
INT32 skin;
+ boolean error;
} md2_t;
extern md2_t md2_models[NUMSPRITES];
diff --git a/src/hardware/r_opengl/r_opengl.c b/src/hardware/r_opengl/r_opengl.c
index 54dd94854..e6ff83e89 100644
--- a/src/hardware/r_opengl/r_opengl.c
+++ b/src/hardware/r_opengl/r_opengl.c
@@ -244,6 +244,7 @@ FUNCPRINTF void DBG_Printf(const char *lpFmt, ...)
#define pglMaterialfv glMaterialfv
/* Raster functions */
+#define pglPixelStorei glPixelStorei
#define pglReadPixels glReadPixels
/* Texture mapping */
@@ -262,15 +263,8 @@ FUNCPRINTF void DBG_Printf(const char *lpFmt, ...)
/* texture mapping */ //GL_EXT_copy_texture
#ifndef KOS_GL_COMPATIBILITY
#define pglCopyTexImage2D glCopyTexImage2D
+#endif
-/* GLU functions */
-#define pgluBuild2DMipmaps gluBuild2DMipmaps
-#endif
-#ifndef MINI_GL_COMPATIBILITY
-/* 1.3 functions for multitexturing */
-#define pglActiveTexture glActiveTexture
-#define pglMultiTexCoord2f glMultiTexCoord2f
-#endif
#else //!STATIC_OPENGL
/* 1.0 functions */
@@ -365,6 +359,8 @@ typedef void (APIENTRY * PFNglMaterialfv) (GLint face, GLenum pname, GLfloat *pa
static PFNglMaterialfv pglMaterialfv;
/* Raster functions */
+typedef void (APIENTRY * PFNglPixelStorei) (GLenum pname, GLint param);
+static PFNglPixelStorei pglPixelStorei;
typedef void (APIENTRY * PFNglReadPixels) (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels);
static PFNglReadPixels pglReadPixels;
@@ -391,7 +387,7 @@ static PFNglBindTexture pglBindTexture;
/* texture mapping */ //GL_EXT_copy_texture
typedef void (APIENTRY * PFNglCopyTexImage2D) (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border);
static PFNglCopyTexImage2D pglCopyTexImage2D;
-
+#endif
/* GLU functions */
typedef GLint (APIENTRY * PFNgluBuild2DMipmaps) (GLenum target, GLint internalFormat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *data);
static PFNgluBuild2DMipmaps pgluBuild2DMipmaps;
@@ -403,7 +399,6 @@ static PFNglActiveTexture pglActiveTexture;
typedef void (APIENTRY *PFNglMultiTexCoord2f) (GLenum, GLfloat, GLfloat);
static PFNglMultiTexCoord2f pglMultiTexCoord2f;
#endif
-#endif
#ifndef MINI_GL_COMPATIBILITY
/* 1.2 Parms */
@@ -494,6 +489,7 @@ boolean SetupGLfunc(void)
GETOPENGLFUNC(pglLightModelfv , glLightModelfv)
GETOPENGLFUNC(pglMaterialfv , glMaterialfv)
+ GETOPENGLFUNC(pglPixelStorei , glPixelStorei)
GETOPENGLFUNC(pglReadPixels , glReadPixels)
GETOPENGLFUNC(pglTexEnvi , glTexEnvi)
@@ -519,35 +515,23 @@ boolean SetupGLfunc(void)
// This has to be done after the context is created so the version number can be obtained
boolean SetupGLFunc13(void)
{
+#ifdef MINI_GL_COMPATIBILITY
+ return false;
+#else
const GLubyte *version = pglGetString(GL_VERSION);
int glmajor, glminor;
gl13 = false;
-#ifdef MINI_GL_COMPATIBILITY
- return false;
-#else
-#ifdef STATIC_OPENGL
- gl13 = true;
-#else
-
// Parse the GL version
if (version != NULL)
{
if (sscanf((const char*)version, "%d.%d", &glmajor, &glminor) == 2)
{
// Look, we gotta prepare for the inevitable arrival of GL 2.0 code...
- switch (glmajor)
- {
- case 1:
- if (glminor == 3) gl13 = true;
- break;
- case 2:
- case 3:
- case 4:
- gl13 = true;
- default:
- break;
- }
+ if (glmajor == 1 && glminor >= 3)
+ gl13 = true;
+ else if (glmajor > 1)
+ gl13 = true;
}
}
@@ -568,9 +552,6 @@ boolean SetupGLFunc13(void)
}
else
DBG_Printf("GL_ARB_multitexture support: disabled\n");
-#undef GETOPENGLFUNC
-
-#endif
return true;
#endif
}
@@ -897,7 +878,9 @@ EXPORT void HWRAPI(ReadRect) (INT32 x, INT32 y, INT32 width, INT32 height,
GLubyte*top = (GLvoid*)dst_data, *bottom = top + dst_stride * (height - 1);
GLubyte *row = malloc(dst_stride);
if (!row) return;
+ pglPixelStorei(GL_PACK_ALIGNMENT, 1);
pglReadPixels(x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, dst_data);
+ pglPixelStorei(GL_UNPACK_ALIGNMENT, 1);
for(i = 0; i < height/2; i++)
{
memcpy(row, top, dst_stride);
@@ -913,7 +896,9 @@ EXPORT void HWRAPI(ReadRect) (INT32 x, INT32 y, INT32 width, INT32 height,
INT32 j;
GLubyte *image = malloc(width*height*3*sizeof (*image));
if (!image) return;
+ pglPixelStorei(GL_PACK_ALIGNMENT, 1);
pglReadPixels(x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, image);
+ pglPixelStorei(GL_UNPACK_ALIGNMENT, 1);
for (i = height-1; i >= 0; i--)
{
for (j = 0; j < width; j++)
@@ -1815,13 +1800,11 @@ EXPORT void HWRAPI(SetSpecialState) (hwdspecialstate_t IdState, INT32 Value)
min_filter = GL_NEAREST;
#endif
}
-#ifndef STATIC_OPENGL
if (!pgluBuild2DMipmaps)
{
MipMap = GL_FALSE;
min_filter = GL_LINEAR;
}
-#endif
Flush(); //??? if we want to change filter mode by texture, remove this
break;
@@ -1836,7 +1819,7 @@ EXPORT void HWRAPI(SetSpecialState) (hwdspecialstate_t IdState, INT32 Value)
}
}
-static inline void DrawMD2Ex(INT32 *gl_cmd_buffer, md2_frame_t *frame, UINT32 duration, UINT32 tics, md2_frame_t *nextframe, FTransform *pos, float scale, UINT8 flipped, UINT8 *color)
+static void DrawMD2Ex(INT32 *gl_cmd_buffer, md2_frame_t *frame, UINT32 duration, UINT32 tics, md2_frame_t *nextframe, FTransform *pos, float scale, UINT8 flipped, UINT8 *color)
{
INT32 val, count, pindex;
GLfloat s, t;
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index ec747305e..365ea093b 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -91,7 +91,7 @@ patch_t *tallminus;
patch_t *emeraldpics[7];
patch_t *tinyemeraldpics[7];
static patch_t *emblemicon;
-static patch_t *tokenicon;
+patch_t *tokenicon;
//-------------------------------------------
// misc vars
@@ -198,21 +198,6 @@ void HU_LoadGraphics(void)
tny_font[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX);
}
- // cache the level title font for entire game execution
- lt_font[0] = (patch_t *)W_CachePatchName("LTFNT039", PU_HUDGFX); /// \note fake start hack
-
- // Number support
- lt_font[9] = (patch_t *)W_CachePatchName("LTFNT048", PU_HUDGFX);
- lt_font[10] = (patch_t *)W_CachePatchName("LTFNT049", PU_HUDGFX);
- lt_font[11] = (patch_t *)W_CachePatchName("LTFNT050", PU_HUDGFX);
- lt_font[12] = (patch_t *)W_CachePatchName("LTFNT051", PU_HUDGFX);
- lt_font[13] = (patch_t *)W_CachePatchName("LTFNT052", PU_HUDGFX);
- lt_font[14] = (patch_t *)W_CachePatchName("LTFNT053", PU_HUDGFX);
- lt_font[15] = (patch_t *)W_CachePatchName("LTFNT054", PU_HUDGFX);
- lt_font[16] = (patch_t *)W_CachePatchName("LTFNT055", PU_HUDGFX);
- lt_font[17] = (patch_t *)W_CachePatchName("LTFNT056", PU_HUDGFX);
- lt_font[18] = (patch_t *)W_CachePatchName("LTFNT057", PU_HUDGFX);
-
j = LT_FONTSTART;
for (i = 0; i < LT_FONTSIZE; i++)
{
@@ -470,7 +455,7 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum)
boolean action = false;
char *ptr;
- CONS_Debug(DBG_NETPLAY,"Recieved SAY cmd from Player %d (%s)\n", playernum+1, player_names[playernum]);
+ CONS_Debug(DBG_NETPLAY,"Received SAY cmd from Player %d (%s)\n", playernum+1, player_names[playernum]);
target = READSINT8(*p);
flags = READUINT8(*p);
@@ -757,15 +742,8 @@ void HU_clearChatChars(void)
//
boolean HU_Responder(event_t *ev)
{
- static boolean shiftdown = false;
UINT8 c;
- if (ev->data1 == KEY_LSHIFT || ev->data1 == KEY_RSHIFT)
- {
- shiftdown = (ev->type == ev_keydown);
- return chat_on;
- }
-
if (ev->type != ev_keydown)
return false;
@@ -797,6 +775,14 @@ boolean HU_Responder(event_t *ev)
}
else // if chat_on
{
+ // Ignore modifier keys
+ // Note that we do this here so users can still set
+ // their chat keys to one of these, if they so desire.
+ if (ev->data1 == KEY_LSHIFT || ev->data1 == KEY_RSHIFT
+ || ev->data1 == KEY_LCTRL || ev->data1 == KEY_RCTRL
+ || ev->data1 == KEY_LALT || ev->data1 == KEY_RALT)
+ return true;
+
c = (UINT8)ev->data1;
// use console translations
@@ -854,7 +840,7 @@ static void HU_DrawChat(void)
else
{
//charwidth = SHORT(hu_font[talk[i]-HU_FONTSTART]->width) * con_scalefactor;
- V_DrawCharacter(HU_INPUTX + c, y, talk[i++] | cv_constextsize.value | V_NOSCALESTART, !cv_allcaps.value);
+ V_DrawCharacter(HU_INPUTX + c, y, talk[i++] | cv_constextsize.value | V_NOSCALESTART, true);
}
c += charwidth;
}
@@ -871,7 +857,7 @@ static void HU_DrawChat(void)
else
{
//charwidth = SHORT(hu_font[w_chat[i]-HU_FONTSTART]->width) * con_scalefactor;
- V_DrawCharacter(HU_INPUTX + c, y, w_chat[i++] | cv_constextsize.value | V_NOSCALESTART | t, !cv_allcaps.value);
+ V_DrawCharacter(HU_INPUTX + c, y, w_chat[i++] | cv_constextsize.value | V_NOSCALESTART | t, true);
}
c += charwidth;
@@ -883,7 +869,7 @@ static void HU_DrawChat(void)
}
if (hu_tick < 4)
- V_DrawCharacter(HU_INPUTX + c, y, '_' | cv_constextsize.value |V_NOSCALESTART|t, !cv_allcaps.value);
+ V_DrawCharacter(HU_INPUTX + c, y, '_' | cv_constextsize.value |V_NOSCALESTART|t, true);
}
@@ -1101,7 +1087,19 @@ void HU_Drawer(void)
// draw desynch text
if (hu_resynching)
- V_DrawCenteredString(BASEVIDWIDTH/2, 180, V_YELLOWMAP, "Resynching...");
+ {
+ static UINT32 resynch_ticker = 0;
+ char resynch_text[14];
+ UINT32 i;
+
+ // Animate the dots
+ resynch_ticker++;
+ strcpy(resynch_text, "Resynching");
+ for (i = 0; i < (resynch_ticker / 16) % 4; i++)
+ strcat(resynch_text, ".");
+
+ V_DrawCenteredString(BASEVIDWIDTH/2, 180, V_YELLOWMAP | V_ALLOWLOWERCASE, resynch_text);
+ }
}
//======================================================================
@@ -1198,7 +1196,7 @@ void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, I
V_DrawString(x + 20, y,
((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0)
- | ((players[tab[i].num].health > 0) ? 0 : V_60TRANS)
+ | ((players[tab[i].num].mo && players[tab[i].num].mo->health > 0) ? 0 : V_60TRANS)
| V_ALLOWLOWERCASE, tab[i].name);
// Draw emeralds
@@ -1208,7 +1206,7 @@ void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, I
HU_DrawEmeralds(x-12,y+2,tab[i].emeralds);
}
- if (players[tab[i].num].health <= 0)
+ if (players[tab[i].num].mo && players[tab[i].num].mo->health <= 0)
V_DrawSmallTranslucentPatch (x, y-4, V_80TRANS, livesback);
else
V_DrawSmallScaledPatch (x, y-4, 0, livesback);
@@ -1220,7 +1218,7 @@ void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, I
V_DrawSmallScaledPatch(x, y-4, 0, superprefix[players[tab[i].num].skin]);
else
{
- if (players[tab[i].num].health <= 0)
+ if (players[tab[i].num].mo && players[tab[i].num].mo->health <= 0)
V_DrawSmallTranslucentPatch(x, y-4, V_80TRANS, faceprefix[players[tab[i].num].skin]);
else
V_DrawSmallScaledPatch(x, y-4, 0, faceprefix[players[tab[i].num].skin]);
@@ -1228,15 +1226,15 @@ void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, I
}
else
{
- if (players[tab[i].num].powers[pw_super])
+ if (players[tab[i].num].powers[pw_super] && players[tab[i].num].mo && (players[tab[i].num].mo->state < &states[S_PLAY_SUPER_TRANS] || players[tab[i].num].mo->state > &states[S_PLAY_SUPER_TRANS9]))
{
- colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo ? players[tab[i].num].mo->color : tab[i].color, GTC_CACHE);
+ colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo->color, GTC_CACHE);
V_DrawSmallMappedPatch (x, y-4, 0, superprefix[players[tab[i].num].skin], colormap);
}
else
{
colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo ? players[tab[i].num].mo->color : tab[i].color, GTC_CACHE);
- if (players[tab[i].num].health <= 0)
+ if (players[tab[i].num].mo && players[tab[i].num].mo->health <= 0)
V_DrawSmallTranslucentMappedPatch (x, y-4, V_80TRANS, faceprefix[players[tab[i].num].skin], colormap);
else
V_DrawSmallMappedPatch (x, y-4, 0, faceprefix[players[tab[i].num].skin], colormap);
@@ -1244,10 +1242,10 @@ void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, I
}
if (G_GametypeUsesLives()) //show lives
- V_DrawRightAlignedString(x, y+4, V_ALLOWLOWERCASE|((players[tab[i].num].health > 0) ? 0 : V_60TRANS), va("%dx", players[tab[i].num].lives));
+ V_DrawRightAlignedString(x, y+4, V_ALLOWLOWERCASE|((players[tab[i].num].mo && players[tab[i].num].mo->health > 0) ? 0 : V_60TRANS), va("%dx", players[tab[i].num].lives));
else if (G_TagGametype() && players[tab[i].num].pflags & PF_TAGIT)
{
- if (players[tab[i].num].health <= 0)
+ if (players[tab[i].num].mo && players[tab[i].num].mo->health <= 0)
V_DrawSmallTranslucentPatch(x-32, y-4, V_60TRANS, tagico);
else
V_DrawSmallScaledPatch(x-32, y-4, 0, tagico);
@@ -1260,13 +1258,13 @@ void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, I
if (players[tab[i].num].exiting)
V_DrawRightAlignedString(x+240, y, 0, va("%i:%02i.%02i", G_TicsToMinutes(players[tab[i].num].realtime,true), G_TicsToSeconds(players[tab[i].num].realtime), G_TicsToCentiseconds(players[tab[i].num].realtime)));
else
- V_DrawRightAlignedString(x+240, y, ((players[tab[i].num].health > 0) ? 0 : V_60TRANS), va("%u", tab[i].count));
+ V_DrawRightAlignedString(x+240, y, ((players[tab[i].num].mo && players[tab[i].num].mo->health > 0) ? 0 : V_60TRANS), va("%u", tab[i].count));
}
else
- V_DrawRightAlignedString(x+240, y, ((players[tab[i].num].health > 0) ? 0 : V_60TRANS), va("%i:%02i.%02i", G_TicsToMinutes(tab[i].count,true), G_TicsToSeconds(tab[i].count), G_TicsToCentiseconds(tab[i].count)));
+ V_DrawRightAlignedString(x+240, y, ((players[tab[i].num].mo && players[tab[i].num].mo->health > 0) ? 0 : V_60TRANS), va("%i:%02i.%02i", G_TicsToMinutes(tab[i].count,true), G_TicsToSeconds(tab[i].count), G_TicsToCentiseconds(tab[i].count)));
}
else
- V_DrawRightAlignedString(x+240, y, ((players[tab[i].num].health > 0) ? 0 : V_60TRANS), va("%u", tab[i].count));
+ V_DrawRightAlignedString(x+240, y, ((players[tab[i].num].mo && players[tab[i].num].mo->health > 0) ? 0 : V_60TRANS), va("%u", tab[i].count));
y += 16;
}
@@ -1311,7 +1309,7 @@ void HU_DrawTeamTabRankings(playersort_t *tab, INT32 whiteplayer)
strlcpy(name, tab[i].name, 9);
V_DrawString(x + 20, y,
((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0)
- | ((players[tab[i].num].health > 0) ? 0 : V_TRANSLUCENT)
+ | ((players[tab[i].num].mo && players[tab[i].num].mo->health > 0) ? 0 : V_TRANSLUCENT)
| V_ALLOWLOWERCASE, name);
if (gametype == GT_CTF)
@@ -1337,12 +1335,12 @@ void HU_DrawTeamTabRankings(playersort_t *tab, INT32 whiteplayer)
else
{
colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo ? players[tab[i].num].mo->color : tab[i].color, GTC_CACHE);
- if (players[tab[i].num].health <= 0)
+ if (players[tab[i].num].mo && players[tab[i].num].mo->health <= 0)
V_DrawSmallTranslucentMappedPatch (x, y-4, 0, faceprefix[players[tab[i].num].skin], colormap);
else
V_DrawSmallMappedPatch (x, y-4, 0, faceprefix[players[tab[i].num].skin], colormap);
}
- V_DrawRightAlignedThinString(x+120, y, ((players[tab[i].num].health > 0) ? 0 : V_TRANSLUCENT), va("%u", tab[i].count));
+ V_DrawRightAlignedThinString(x+120, y, ((players[tab[i].num].mo && players[tab[i].num].mo->health > 0) ? 0 : V_TRANSLUCENT), va("%u", tab[i].count));
}
}
@@ -1367,7 +1365,7 @@ void HU_DrawDualTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scoreline
strlcpy(name, tab[i].name, 9);
V_DrawString(x + 20, y,
((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0)
- | ((players[tab[i].num].health > 0) ? 0 : V_TRANSLUCENT)
+ | ((players[tab[i].num].mo && players[tab[i].num].mo->health > 0) ? 0 : V_TRANSLUCENT)
| V_ALLOWLOWERCASE, name);
if (G_GametypeUsesLives()) //show lives
@@ -1390,7 +1388,7 @@ void HU_DrawDualTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scoreline
V_DrawSmallScaledPatch (x, y-4, 0, superprefix[players[tab[i].num].skin]);
else
{
- if (players[tab[i].num].health <= 0)
+ if (players[tab[i].num].mo && players[tab[i].num].mo->health <= 0)
V_DrawSmallTranslucentPatch (x, y-4, 0, faceprefix[players[tab[i].num].skin]);
else
V_DrawSmallScaledPatch (x, y-4, 0, faceprefix[players[tab[i].num].skin]);
@@ -1406,7 +1404,7 @@ void HU_DrawDualTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scoreline
else
{
colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo ? players[tab[i].num].mo->color : tab[i].color, GTC_CACHE);
- if (players[tab[i].num].health <= 0)
+ if (players[tab[i].num].mo && players[tab[i].num].mo->health <= 0)
V_DrawSmallTranslucentMappedPatch (x, y-4, 0, faceprefix[players[tab[i].num].skin], colormap);
else
V_DrawSmallMappedPatch (x, y-4, 0, faceprefix[players[tab[i].num].skin], colormap);
@@ -1421,13 +1419,13 @@ void HU_DrawDualTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scoreline
if (players[tab[i].num].exiting)
V_DrawRightAlignedThinString(x+156, y, 0, va("%i:%02i.%02i", G_TicsToMinutes(players[tab[i].num].realtime,true), G_TicsToSeconds(players[tab[i].num].realtime), G_TicsToCentiseconds(players[tab[i].num].realtime)));
else
- V_DrawRightAlignedThinString(x+156, y, ((players[tab[i].num].health > 0) ? 0 : V_TRANSLUCENT), va("%u", tab[i].count));
+ V_DrawRightAlignedThinString(x+156, y, ((players[tab[i].num].mo && players[tab[i].num].mo->health > 0) ? 0 : V_TRANSLUCENT), va("%u", tab[i].count));
}
else
- V_DrawRightAlignedThinString(x+156, y, ((players[tab[i].num].health > 0) ? 0 : V_TRANSLUCENT), va("%i:%02i.%02i", G_TicsToMinutes(tab[i].count,true), G_TicsToSeconds(tab[i].count), G_TicsToCentiseconds(tab[i].count)));
+ V_DrawRightAlignedThinString(x+156, y, ((players[tab[i].num].mo && players[tab[i].num].mo->health > 0) ? 0 : V_TRANSLUCENT), va("%i:%02i.%02i", G_TicsToMinutes(tab[i].count,true), G_TicsToSeconds(tab[i].count), G_TicsToCentiseconds(tab[i].count)));
}
else
- V_DrawRightAlignedThinString(x+120, y, ((players[tab[i].num].health > 0) ? 0 : V_TRANSLUCENT), va("%u", tab[i].count));
+ V_DrawRightAlignedThinString(x+120, y, ((players[tab[i].num].mo && players[tab[i].num].mo->health > 0) ? 0 : V_TRANSLUCENT), va("%u", tab[i].count));
y += 16;
if (y > 160)
diff --git a/src/hu_stuff.h b/src/hu_stuff.h
index 7b22f33f1..9bfb42912 100644
--- a/src/hu_stuff.h
+++ b/src/hu_stuff.h
@@ -21,7 +21,7 @@
//------------------------------------
// heads up font
//------------------------------------
-#define HU_FONTSTART '\x1F' // the first font character
+#define HU_FONTSTART '\x19' // the first font character
#define HU_FONTEND '~'
#define HU_FONTSIZE (HU_FONTEND - HU_FONTSTART + 1)
@@ -71,6 +71,7 @@ extern patch_t *rmatcico;
extern patch_t *bmatcico;
extern patch_t *tagico;
extern patch_t *tallminus;
+extern patch_t *tokenicon;
// set true when entering a chat message
extern boolean chat_on;
@@ -78,9 +79,6 @@ extern boolean chat_on;
// set true whenever the tab rankings are being shown for any reason
extern boolean hu_showscores;
-// P_DeathThink sets this true to show scores while dead, in multiplayer
-extern boolean playerdeadview;
-
// init heads up data at game startup.
void HU_Init(void);
diff --git a/src/i_net.h b/src/i_net.h
index e378f5723..2bfa5eac7 100644
--- a/src/i_net.h
+++ b/src/i_net.h
@@ -85,7 +85,7 @@ extern doomcom_t *doomcom;
/** \brief return packet in doomcom struct
*/
-extern void (*I_NetGet)(void);
+extern boolean (*I_NetGet)(void);
/** \brief ask to driver if there is data waiting
*/
diff --git a/src/i_system.h b/src/i_system.h
index c161851e0..d61f2d16e 100644
--- a/src/i_system.h
+++ b/src/i_system.h
@@ -296,6 +296,14 @@ char *I_GetEnv(const char *name);
INT32 I_PutEnv(char *variable);
+/** \brief Put data in system clipboard
+*/
+INT32 I_ClipboardCopy(const char *data, size_t size);
+
+/** \brief Retrieve data from system clipboard
+*/
+const char *I_ClipboardPaste(void);
+
void I_RegisterSysCommands(void);
#endif
diff --git a/src/i_tcp.c b/src/i_tcp.c
index f6212458b..c65a536a8 100644
--- a/src/i_tcp.c
+++ b/src/i_tcp.c
@@ -179,6 +179,7 @@ static UINT8 UPNP_support = TRUE;
#include "i_system.h"
#include "i_net.h"
#include "d_net.h"
+#include "d_netfil.h"
#include "i_tcp.h"
#include "m_argv.h"
@@ -482,21 +483,12 @@ static boolean SOCK_cmpaddr(mysockaddr_t *a, mysockaddr_t *b, UINT8 mask)
return false;
}
-static SINT8 getfreenode(void)
-{
- SINT8 j;
-
- for (j = 0; j < MAXNETNODES; j++)
- if (!nodeconnected[j])
- {
- nodeconnected[j] = true;
- return j;
- }
- return -1;
-}
-
// This is a hack. For some reason, nodes aren't being freed properly.
// This goes through and cleans up what nodes were supposed to be freed.
+/** \warning This function causes the file downloading to stop if someone joins.
+ * How? Because it removes nodes that are connected but not in game,
+ * which is exactly what clients downloading a file are.
+ */
static void cleanupnodes(void)
{
SINT8 j;
@@ -506,13 +498,81 @@ static void cleanupnodes(void)
// Why can't I start at zero?
for (j = 1; j < MAXNETNODES; j++)
+ //if (!(nodeingame[j] || SV_SendingFile(j)))
if (!nodeingame[j])
nodeconnected[j] = false;
}
+
+static SINT8 getfreenode(void)
+{
+ SINT8 j;
+
+ cleanupnodes();
+
+ for (j = 0; j < MAXNETNODES; j++)
+ if (!nodeconnected[j])
+ {
+ nodeconnected[j] = true;
+ return j;
+ }
+
+ /** \warning No free node? Just in case a node might not have been freed properly,
+ * look if there are connected nodes that aren't in game, and forget them.
+ * It's dirty, and might result in a poor guy having to restart
+ * downloading a needed wad, but it's better than not letting anyone join...
+ */
+ /*I_Error("No more free nodes!!1!11!11!!1111\n");
+ for (j = 1; j < MAXNETNODES; j++)
+ if (!nodeingame[j])
+ return j;*/
+
+ return -1;
+}
+
+#ifdef _DEBUG
+void Command_Numnodes(void)
+{
+ INT32 connected = 0;
+ INT32 ingame = 0;
+ INT32 i;
+
+ for (i = 1; i < MAXNETNODES; i++)
+ {
+ if (!(nodeconnected[i] || nodeingame[i]))
+ continue;
+
+ if (nodeconnected[i])
+ connected++;
+ if (nodeingame[i])
+ ingame++;
+
+ CONS_Printf("%2d - ", i);
+ if (nodetoplayer[i] != -1)
+ CONS_Printf("player %.2d", nodetoplayer[i]);
+ else
+ CONS_Printf(" ");
+ if (nodeconnected[i])
+ CONS_Printf(" - connected");
+ else
+ CONS_Printf(" - ");
+ if (nodeingame[i])
+ CONS_Printf(" - ingame");
+ else
+ CONS_Printf(" - ");
+ CONS_Printf(" - %s\n", I_GetNodeAddress(i));
+ }
+
+ CONS_Printf("\n"
+ "Connected: %d\n"
+ "Ingame: %d\n",
+ connected, ingame);
+}
+#endif
#endif
#ifndef NONET
-static void SOCK_Get(void)
+// Returns true if a packet was received from a new node, false in all other cases
+static boolean SOCK_Get(void)
{
size_t i, n;
int j;
@@ -535,13 +595,12 @@ static void SOCK_Get(void)
doomcom->remotenode = (INT16)j; // good packet from a game player
doomcom->datalength = (INT16)c;
nodesocket[j] = mysockets[n];
- return;
+ return false;
}
}
// not found
// find a free slot
- cleanupnodes();
j = getfreenode();
if (j > 0)
{
@@ -564,14 +623,15 @@ static void SOCK_Get(void)
}
if (i == numbans)
SOCK_bannednode[j] = false;
- return;
+ return true;
}
else
DEBFILE("New node detected: No more free slots\n");
-
}
}
+
doomcom->remotenode = -1; // no packet
+ return false;
}
#endif
@@ -1256,7 +1316,6 @@ static SINT8 SOCK_NetMakeNodewPort(const char *address, const char *port)
gaie = I_getaddrinfo(address, port, &hints, &ai);
if (gaie == 0)
{
- cleanupnodes();
newnode = getfreenode();
}
if (newnode == -1)
diff --git a/src/info.c b/src/info.c
index 04a28e7ca..9e779acb0 100644
--- a/src/info.c
+++ b/src/info.c
@@ -25,35 +25,368 @@
#endif
// Hey, moron! If you change this table, don't forget about the sprite enum in info.h and the sprite lights in hw_light.c!
+// For the sake of constant merge conflicts, let's spread this out
char sprnames[NUMSPRITES + 1][5] =
{
- "NULL","UNKN","THOK","PLAY","POSS","SPOS","FISH","BUZZ","RBUZ","JETB",
- "JETW","JETG","CCOM","DETN","SKIM","TRET","TURR","SHRP","JJAW","SNLR",
- "VLTR","PNTY","ARCH","CBFS","SPSH","ESHI","GSNP","MNUS","SSHL","UNID",
- "BBUZ","JETF","EGGM","EGGN","TNKA","TNKB","SPNK","GOOP","EGGO","PRPL",
- "FAKE","EGGP","EFIR","EGGQ","EGGR","BRAK","BGOO","BMSL","EGGT","RCKT",
- "ELEC","TARG","NPLM","MNPL","METL","MSCF","MSCB","RING","TRNG","EMMY",
- "TOKE","RFLG","BFLG","NWNG","EMBM","CEMG","EMER","FANS","BBLS","SIGN",
- "STEM","SPIK","SFLM","USPK","STPT","BMNE","SRBX","RRBX","BRBX","SHTV",
- "PINV","YLTV","BLTV","BKTV","WHTV","GRTV","ELTV","EGGB","MIXU","RECY",
- "QUES","GBTV","PRUP","PTTV","MTEX","MISL","TORP","ENRG","MINE","JBUL",
- "TRLS","CBLL","AROW","CFIR","FWR1","FWR2","FWR3","FWR4","BUS1","BUS2",
- "THZP","ALRM","GARG","SEWE","DRIP","CRL1","CRL2","CRL3","BCRY","CHAN",
- "FLAM","ESTA","SMCH","BMCH","SMCE","BMCE","BTBL","STBL","CACT","FLME",
- "DFLM","XMS1","XMS2","XMS3","BSZ1","BSZ2","BSZ3","BSZ4","BSZ5","BSZ6",
- "BSZ7","BSZ8","STLG","DBAL","RCRY","ARMA","ARMF","ARMB","WIND","MAGN",
- "ELEM","FORC","PITY","IVSP","SSPK","GOAL","BIRD","BUNY","MOUS","CHIC",
- "COWZ","RBRD","SPRY","SPRR","SPRB","YSPR","RSPR","SSWY","SSWR","SSWB",
- "RAIN","SNO1","SPLH","SPLA","SMOK","BUBL","WZAP","TFOG","SEED","PRTL",
- "SCOR","DRWN","TTAG","GFLG","RRNG","RNGB","RNGR","RNGI","RNGA","RNGE",
- "RNGS","RNGG","PIKB","PIKR","PIKA","PIKE","PIKS","PIKG","TAUT","TGRE",
- "TSCR","COIN","CPRK","GOOM","BGOM","FFWR","FBLL","SHLL","PUMA","HAMM",
- "KOOP","BFLM","MAXE","MUS1","MUS2","TOAD","NDRN","NSPK","NBMP","HOOP",
- "NSCR","NPRU","CAPS","SPRK","BOM1","BOM2","BOM3","BOM4","ROIA","ROIB",
- "ROIC","ROID","ROIE","ROIF","ROIG","ROIH","ROII","ROIJ","ROIK","ROIL",
- "ROIM","ROIN","ROIO","ROIP","BBAL","GWLG","GWLR","SRBA","SRBB","SRBC",
- "SRBD","SRBE","SRBF","SRBG","SRBH","SRBI","SRBJ","SRBK","SRBL","SRBM",
- "SRBN","SRBO",
+ "NULL", // invisible object
+ "UNKN",
+
+ "THOK", // Thok! mobj
+ "PLAY",
+
+ // Enemies
+ "POSS",
+ "SPOS",
+ "FISH", // Greenflower Fish
+ "BUZZ", // Buzz (Gold)
+ "RBUZ", // Buzz (Red)
+ "JETB", // Jetty-Syn Bomber
+ "JETW", // Jetty-Syn Water Bomber
+ "JETG", // Jetty-Syn Gunner
+ "CCOM", // Crawla Commander
+ "DETN", // Deton
+ "SKIM", // Skim mine dropper
+ "TRET",
+ "TURR", // Pop-Up Turret
+ "SHRP", // Sharp
+ "JJAW", // Jet Jaw
+ "SNLR", // Snailer
+ "VLTR", // Vulture
+ "PNTY", // Pointy
+ "ARCH", // Robo-Hood
+ "CBFS", // CastleBot FaceStabber (Egg Knight?)
+ "SPSH", // Egg Guard
+ "ESHI", // Egg Shield for Egg Guard
+ "GSNP", // Green Snapper
+ "MNUS", // Minus
+ "SSHL", // Spring Shell
+ "UNID", // Unidus
+ "BBUZ", // AquaBuzz, for Azure Temple
+
+ // Generic Boss Items
+ "JETF", // Boss jet fumes
+
+ // Boss 1 (Greenflower)
+ "EGGM",
+
+ // Boss 2 (Techno Hill)
+ "EGGN", // Boss 2
+ "TNKA", // Boss 2 Tank 1
+ "TNKB", // Boss 2 Tank 2
+ "SPNK", // Boss 2 Spigot
+ "GOOP", // Boss 2 Goop
+
+ // Boss 3 (Deep Sea)
+ "EGGO", // Boss 3
+ "PRPL", // Boss 3 Propeller
+ "FAKE", // Boss 3 Fakemobile
+
+ // Boss 4 (Castle Eggman)
+ "EGGP",
+ "EFIR", // Boss 4 jet flame
+
+ // Boss 5 (Arid Canyon)
+ "EGGQ",
+
+ // Boss 6 (Red Volcano)
+ "EGGR",
+
+ // Boss 7 (Dark City)
+ "BRAK",
+ "BGOO", // Goop
+ "BMSL",
+
+ // Boss 8 (Egg Rock)
+ "EGGT",
+
+ // Cy-Brak-Demon; uses "BRAK" as well, but has some extras
+ "RCKT", // Rockets!
+ "ELEC", // Electricity!
+ "TARG", // Targeting reticules!
+ "NPLM", // Big napalm bombs!
+ "MNPL", // Mini napalm bombs!
+
+ // Metal Sonic
+ "METL",
+ "MSCF",
+ "MSCB",
+
+ // Collectible Items
+ "RING",
+ "TRNG", // Team Rings
+ "EMMY", // emerald test
+ "TOKE", // Special Stage Token
+ "RFLG", // Red CTF Flag
+ "BFLG", // Blue CTF Flag
+ "NWNG", // NiGHTS Wing collectable item.
+ "EMBM", // Emblem
+ "CEMG", // Chaos Emeralds
+ "EMER", // Emerald Hunt
+
+ // Interactive Objects
+ "FANS",
+ "BBLS", // water bubble source
+ "SIGN", // Level end sign
+ "STEM", // Steam riser
+ "SPIK", // Spike Ball
+ "SFLM", // Spin fire
+ "USPK", // Floor spike
+ "STPT", // Starpost
+ "BMNE", // Big floating mine
+
+ // Monitor Boxes
+ "MSTV", // MiSc TV sprites
+ "XLTV", // eXtra Large TV sprites
+
+ "TRRI", // Red team: 10 RIngs
+ "TBRI", // Blue team: 10 RIngs
+
+ "TVRI", // 10 RIng
+ "TVPI", // PIty shield
+ "TVAT", // ATtraction shield
+ "TVFO", // FOrce shield
+ "TVAR", // ARmageddon shield
+ "TVWW", // WhirlWind shield
+ "TVEL", // ELemental shield
+ "TVSS", // Super Sneakers
+ "TVIV", // InVincibility
+ "TV1U", // 1Up
+ "TV1P", // 1uP (textless)
+ "TVEG", // EGgman
+ "TVMX", // MiXup
+ "TVMY", // MYstery
+ "TVGV", // GraVity boots
+ "TVRC", // ReCycler
+ "TV1K", // 1,000 points (1 K)
+ "TVTK", // 10,000 points (Ten K)
+ "TVFL", // FLame shield
+ "TVBB", // BuBble shield
+ "TVZP", // Thunder shield (ZaP)
+
+ // Projectiles
+ "MISL",
+ "TORP", // Torpedo
+ "ENRG", // Energy ball
+ "MINE", // Skim mine
+ "JBUL", // Jetty-Syn Bullet
+ "TRLS",
+ "CBLL", // Cannonball
+ "AROW", // Arrow
+ "CFIR", // Colored fire of various sorts
+
+ // Greenflower Scenery
+ "FWR1",
+ "FWR2", // GFZ Sunflower
+ "FWR3", // GFZ budding flower
+ "FWR4",
+ "BUS1", // GFZ Bush w/ berries
+ "BUS2", // GFZ Bush w/o berries
+
+ // Techno Hill Scenery
+ "THZP", // Techno Hill Zone Plant
+ "FWR5", // Another one
+ "ALRM", // THZ2 Alarm
+
+ // Deep Sea Scenery
+ "GARG", // Deep Sea Gargoyle
+ "SEWE", // Deep Sea Seaweed
+ "DRIP", // Dripping water
+ "CRL1", // Coral 1
+ "CRL2", // Coral 2
+ "CRL3", // Coral 3
+ "BCRY", // Blue Crystal
+
+ // Castle Eggman Scenery
+ "CHAN", // CEZ Chain
+ "FLAM", // Flame
+ "ESTA", // Eggman esta una estatua!
+ "SMCH", // Small Mace Chain
+ "BMCH", // Big Mace Chain
+ "SMCE", // Small Mace
+ "BMCE", // Big Mace
+
+ // Arid Canyon Scenery
+ "BTBL", // Big tumbleweed
+ "STBL", // Small tumbleweed
+ "CACT", // Cacti sprites
+
+ // Red Volcano Scenery
+ "FLME", // Flame jet
+ "DFLM", // Blade's flame
+
+ // Dark City Scenery
+
+ // Egg Rock Scenery
+
+ // Christmas Scenery
+ "XMS1", // Christmas Pole
+ "XMS2", // Candy Cane
+ "XMS3", // Snowman
+ "XMS4", // Lamppost
+ "XMS5", // Hanging Star
+
+ // Botanic Serenity Scenery
+ "BSZ1", // Tall flowers
+ "BSZ2", // Medium flowers
+ "BSZ3", // Small flowers
+ "BSZ4", // Tulip
+ "BSZ5", // Cluster of Tulips
+ "BSZ6", // Bush
+ "BSZ7", // Vine
+ "BSZ8", // Misc things
+
+ // Misc Scenery
+ "STLG", // Stalagmites
+ "DBAL", // Disco
+ "RCRY", // ATZ Red Crystal (Target)
+
+ // Powerup Indicators
+ "ARMA", // Armageddon Shield Orb
+ "ARMF", // Armageddon Shield Ring, Front
+ "ARMB", // Armageddon Shield Ring, Back
+ "WIND", // Whirlwind Shield Orb
+ "MAGN", // Attract Shield Orb
+ "ELEM", // Elemental Shield Orb and Fire
+ "FORC", // Force Shield Orb
+ "PITY", // Pity Shield Orb
+ "FIRS", // Flame Shield Orb
+ "BUBS", // Bubble Shield Orb
+ "ZAPS", // Thunder Shield Orb
+ "IVSP", // invincibility sparkles
+ "SSPK", // Super Sonic Spark
+
+ "GOAL", // Special Stage goal (here because lol NiGHTS)
+
+ // Flickies
+ "FBUB", // Flicky-sized bubble
+ "FL01", // Bluebird
+ "FL02", // Rabbit
+ "FL03", // Chicken
+ "FL04", // Seal
+ "FL05", // Pig
+ "FL06", // Chipmunk
+ "FL07", // Penguin
+ "FL08", // Fish
+ "FL09", // Ram
+ "FL10", // Puffin
+ "FL11", // Cow
+ "FL12", // Rat
+ "FL13", // Bear
+ "FL14", // Dove
+ "FL15", // Cat
+ "FL16", // Canary
+
+ // Springs
+ "SPRY", // yellow spring
+ "SPRR", // red spring
+ "SPRB", // Blue springs
+ "YSPR", // Yellow Diagonal Spring
+ "RSPR", // Red Diagonal Spring
+ "SSWY", // Yellow Side Spring
+ "SSWR", // Red Side Spring
+ "SSWB", // Blue Side Spring
+
+ // Environmental Effects
+ "RAIN", // Rain
+ "SNO1", // Snowflake
+ "SPLH", // Water Splish
+ "SPLA", // Water Splash
+ "SMOK",
+ "BUBL", // Bubble
+ "WZAP",
+ "DUST", // Spindash dust
+ "FPRT", // Spindash dust (flame)
+ "TFOG", // Teleport Fog
+ "SEED", // Sonic CD flower seed
+ "PRTL", // Particle (for fans, etc.)
+
+ // Game Indicators
+ "SCOR", // Score logo
+ "DRWN", // Drowning Timer
+ "LCKN", // Target
+ "TTAG", // Tag Sign
+ "GFLG", // Got Flag sign
+
+ "CORK",
+
+ // Ring Weapons
+ "RRNG", // Red Ring
+ "RNGB", // Bounce Ring
+ "RNGR", // Rail Ring
+ "RNGI", // Infinity Ring
+ "RNGA", // Automatic Ring
+ "RNGE", // Explosion Ring
+ "RNGS", // Scatter Ring
+ "RNGG", // Grenade Ring
+
+ "PIKB", // Bounce Ring Pickup
+ "PIKR", // Rail Ring Pickup
+ "PIKA", // Automatic Ring Pickup
+ "PIKE", // Explosion Ring Pickup
+ "PIKS", // Scatter Ring Pickup
+ "PIKG", // Grenade Ring Pickup
+
+ "TAUT", // Thrown Automatic Ring
+ "TGRE", // Thrown Grenade Ring
+ "TSCR", // Thrown Scatter Ring
+
+ // Mario-specific stuff
+ "COIN",
+ "CPRK",
+ "GOOM",
+ "BGOM",
+ "FFWR",
+ "FBLL",
+ "SHLL",
+ "PUMA",
+ "HAMM",
+ "KOOP",
+ "BFLM",
+ "MAXE",
+ "MUS1",
+ "MUS2",
+ "TOAD",
+
+ // NiGHTS Stuff
+ "NDRN", // NiGHTS drone
+ "NSPK", // NiGHTS sparkle
+ "NBMP", // NiGHTS Bumper
+ "HOOP", // NiGHTS hoop sprite
+ "NSCR", // NiGHTS score sprite
+ "NPRU", // Nights Powerups
+ "CAPS", // Capsule thingy for NiGHTS
+
+ // Debris
+ "SPRK", // spark
+ "BOM1", // Robot Explosion
+ "BOM2", // Boss Explosion 1
+ "BOM3", // Boss Explosion 2
+ "BOM4", // Underwater Explosion
+
+ // Crumbly rocks
+ "ROIA",
+ "ROIB",
+ "ROIC",
+ "ROID",
+ "ROIE",
+ "ROIF",
+ "ROIG",
+ "ROIH",
+ "ROII",
+ "ROIJ",
+ "ROIK",
+ "ROIL",
+ "ROIM",
+ "ROIN",
+ "ROIO",
+ "ROIP",
+
+ // Blue Spheres
+ "BBAL",
+
+ // Gravity Well Objects
+ "GWLG",
+ "GWLR",
};
char spr2names[NUMPLAYERSPRITES][5] =
@@ -62,12 +395,12 @@ char spr2names[NUMPLAYERSPRITES][5] =
"WAIT",
"WALK",
"RUN_",
- "PEEL",
+ "DASH",
"PAIN",
+ "STUN",
"DEAD",
"DRWN",
- "SPIN",
- "DASH",
+ "ROLL",
"GASP",
"JUMP",
"SPNG",
@@ -75,8 +408,7 @@ char spr2names[NUMPLAYERSPRITES][5] =
"EDGE",
"RIDE",
- "SIGN",
- "LIFE",
+ "SPIN",
"FLY_",
"SWIM",
@@ -86,32 +418,24 @@ char spr2names[NUMPLAYERSPRITES][5] =
"CLNG",
"CLMB",
+ "FLT_",
+ "FRUN",
+
+ "BNCE",
+ "BLND",
+
+ "FIRE",
+
"TWIN",
"MLEE",
+ "MLEL",
"TRNS",
- "SSTD",
- "SWLK",
- "SRUN",
- "SPEE",
- "SPAN",
- "SSTN",
- "SDTH",
- "SDRN",
- "SSPN",
- "SGSP",
- "SJMP",
- "SSPG",
- "SFAL",
- "SEDG",
- "SRID",
- "SFLT",
- "NTRN",
"NSTD",
"NFLT",
- "NPAN",
+ "NSTN",
"NPUL",
"NATK",
@@ -141,7 +465,10 @@ char spr2names[NUMPLAYERSPRITES][5] =
"DRL9",
"DRLA",
"DRLB",
- "DRLC"
+ "DRLC",
+
+ "SIGN",
+ "LIFE"
};
enum playersprite free_spr2 = SPR2_FIRSTFREESLOT;
@@ -149,8 +476,8 @@ enum playersprite free_spr2 = SPR2_FIRSTFREESLOT;
state_t states[NUMSTATES] =
{
// frame is masked through FF_FRAMEMASK
- // FF_ANIMATE (0x4000) makes simple state animations (var1 #frames, var2 tic delay)
- // FF_FULLBRIGHT (0x8000) activates the fullbright colormap
+ // FF_ANIMATE makes simple state animations (var1 #frames, var2 tic delay)
+ // FF_FULLBRIGHT activates the fullbright colormap
// use FF_TRANS10 - FF_TRANS90 for easy translucency
// (or tr_trans10< "player_t"
+// or players[0].powers -> "player_t.powers"
+static int lib_userdataType(lua_State *L)
+{
+ lua_settop(L, 1); // pop everything except arg 1 (in case somebody decided to add more)
+ luaL_checktype(L, 1, LUA_TUSERDATA);
+ lua_pushstring(L, GetUserdataUType(L));
+ return 1;
+}
+
// M_RANDOM
//////////////
@@ -205,6 +287,41 @@ static int lib_pClosestPointOnLine(lua_State *L)
return 2;
}
+static int lib_pPointOnLineSide(lua_State *L)
+{
+ int n = lua_gettop(L);
+ fixed_t x = luaL_checkfixed(L, 1);
+ fixed_t y = luaL_checkfixed(L, 2);
+ //HUDSAFE
+ if (lua_isuserdata(L, 3)) // use a real linedef to get our points
+ {
+ line_t *line = *((line_t **)luaL_checkudata(L, 3, META_LINE));
+ if (!line)
+ return LUA_ErrInvalid(L, "line_t");
+ lua_pushinteger(L, P_PointOnLineSide(x, y, line));
+ }
+ else // use custom coordinates of our own!
+ {
+ vertex_t v1, v2; // fake vertexes
+ line_t junk; // fake linedef
+
+ if (n < 6)
+ return luaL_error(L, "arguments 3 to 6 not all given (expected 4 fixed-point integers)");
+
+ v1.x = luaL_checkfixed(L, 3);
+ v1.y = luaL_checkfixed(L, 4);
+ v2.x = luaL_checkfixed(L, 5);
+ v2.y = luaL_checkfixed(L, 6);
+
+ junk.v1 = &v1;
+ junk.v2 = &v2;
+ junk.dx = v2.x - v1.x;
+ junk.dy = v2.y - v1.y;
+ lua_pushinteger(L, P_PointOnLineSide(x, y, &junk));
+ }
+ return 1;
+}
+
// P_ENEMY
/////////////
@@ -212,6 +329,7 @@ static int lib_pCheckMeleeRange(lua_State *L)
{
mobj_t *actor = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
NOHUD
+ INLEVEL
if (!actor)
return LUA_ErrInvalid(L, "mobj_t");
lua_pushboolean(L, P_CheckMeleeRange(actor));
@@ -222,6 +340,7 @@ static int lib_pJetbCheckMeleeRange(lua_State *L)
{
mobj_t *actor = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
NOHUD
+ INLEVEL
if (!actor)
return LUA_ErrInvalid(L, "mobj_t");
lua_pushboolean(L, P_JetbCheckMeleeRange(actor));
@@ -232,6 +351,7 @@ static int lib_pFaceStabCheckMeleeRange(lua_State *L)
{
mobj_t *actor = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
NOHUD
+ INLEVEL
if (!actor)
return LUA_ErrInvalid(L, "mobj_t");
lua_pushboolean(L, P_FaceStabCheckMeleeRange(actor));
@@ -242,6 +362,7 @@ static int lib_pSkimCheckMeleeRange(lua_State *L)
{
mobj_t *actor = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
NOHUD
+ INLEVEL
if (!actor)
return LUA_ErrInvalid(L, "mobj_t");
lua_pushboolean(L, P_SkimCheckMeleeRange(actor));
@@ -252,6 +373,7 @@ static int lib_pCheckMissileRange(lua_State *L)
{
mobj_t *actor = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
NOHUD
+ INLEVEL
if (!actor)
return LUA_ErrInvalid(L, "mobj_t");
lua_pushboolean(L, P_CheckMissileRange(actor));
@@ -262,6 +384,7 @@ static int lib_pNewChaseDir(lua_State *L)
{
mobj_t *actor = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
NOHUD
+ INLEVEL
if (!actor)
return LUA_ErrInvalid(L, "mobj_t");
P_NewChaseDir(actor);
@@ -275,6 +398,7 @@ static int lib_pLookForPlayers(lua_State *L)
boolean allaround = lua_optboolean(L, 3);
boolean tracer = lua_optboolean(L, 4);
NOHUD
+ INLEVEL
if (!actor)
return LUA_ErrInvalid(L, "mobj_t");
lua_pushboolean(L, P_LookForPlayers(actor, allaround, tracer, dist));
@@ -291,6 +415,7 @@ static int lib_pSpawnMobj(lua_State *L)
fixed_t z = luaL_checkfixed(L, 3);
mobjtype_t type = luaL_checkinteger(L, 4);
NOHUD
+ INLEVEL
if (type >= NUMMOBJTYPES)
return luaL_error(L, "mobj type %d out of range (0 - %d)", type, NUMMOBJTYPES-1);
LUA_PushUserdata(L, P_SpawnMobj(x, y, z, type), META_MOBJ);
@@ -301,6 +426,7 @@ static int lib_pRemoveMobj(lua_State *L)
{
mobj_t *th = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
NOHUD
+ INLEVEL
if (!th)
return LUA_ErrInvalid(L, "mobj_t");
if (th->player)
@@ -309,12 +435,49 @@ static int lib_pRemoveMobj(lua_State *L)
return 0;
}
+// P_IsValidSprite2 technically doesn't exist, and probably never should... but too much would need to be exposed to allow this to be checked by other methods.
+
+static int lib_pIsValidSprite2(lua_State *L)
+{
+ mobj_t *mobj = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
+ UINT8 spr2 = (UINT8)luaL_checkinteger(L, 2);
+ //HUDSAFE
+ INLEVEL
+ if (!mobj)
+ return LUA_ErrInvalid(L, "mobj_t");
+ lua_pushboolean(L, (mobj->skin && (((skin_t *)mobj->skin)->sprites[spr2].numframes)));
+ return 1;
+}
+
+// P_SpawnLockOn doesn't exist either, but we want to expose making a local mobj without encouraging hacks.
+
+static int lib_pSpawnLockOn(lua_State *L)
+{
+ player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
+ mobj_t *lockon = *((mobj_t **)luaL_checkudata(L, 2, META_MOBJ));
+ statenum_t state = luaL_checkinteger(L, 3);
+ NOHUD
+ INLEVEL
+ if (!lockon)
+ return LUA_ErrInvalid(L, "mobj_t");
+ if (!player)
+ return LUA_ErrInvalid(L, "player_t");
+ if (P_IsLocalPlayer(player)) // Only display it on your own view.
+ {
+ mobj_t *visual = P_SpawnMobj(lockon->x, lockon->y, lockon->z, MT_LOCKON); // positioning, flip handled in P_SceneryThinker
+ visual->target = lockon;
+ P_SetMobjStateNF(visual, state);
+ }
+ return 0;
+}
+
static int lib_pSpawnMissile(lua_State *L)
{
mobj_t *source = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
mobj_t *dest = *((mobj_t **)luaL_checkudata(L, 2, META_MOBJ));
mobjtype_t type = luaL_checkinteger(L, 3);
NOHUD
+ INLEVEL
if (!source || !dest)
return LUA_ErrInvalid(L, "mobj_t");
if (type >= NUMMOBJTYPES)
@@ -332,6 +495,7 @@ static int lib_pSpawnXYZMissile(lua_State *L)
fixed_t y = luaL_checkfixed(L, 5);
fixed_t z = luaL_checkfixed(L, 6);
NOHUD
+ INLEVEL
if (!source || !dest)
return LUA_ErrInvalid(L, "mobj_t");
if (type >= NUMMOBJTYPES)
@@ -351,6 +515,7 @@ static int lib_pSpawnPointMissile(lua_State *L)
fixed_t y = luaL_checkfixed(L, 7);
fixed_t z = luaL_checkfixed(L, 8);
NOHUD
+ INLEVEL
if (!source)
return LUA_ErrInvalid(L, "mobj_t");
if (type >= NUMMOBJTYPES)
@@ -368,6 +533,7 @@ static int lib_pSpawnAlteredDirectionMissile(lua_State *L)
fixed_t z = luaL_checkfixed(L, 5);
INT32 shiftingAngle = (INT32)luaL_checkinteger(L, 5);
NOHUD
+ INLEVEL
if (!source)
return LUA_ErrInvalid(L, "mobj_t");
if (type >= NUMMOBJTYPES)
@@ -381,6 +547,7 @@ static int lib_pColorTeamMissile(lua_State *L)
mobj_t *missile = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
player_t *source = *((player_t **)luaL_checkudata(L, 2, META_PLAYER));
NOHUD
+ INLEVEL
if (!missile)
return LUA_ErrInvalid(L, "mobj_t");
if (!source)
@@ -397,6 +564,7 @@ static int lib_pSPMAngle(lua_State *L)
UINT8 allowaim = (UINT8)luaL_optinteger(L, 4, 0);
UINT32 flags2 = (UINT32)luaL_optinteger(L, 5, 0);
NOHUD
+ INLEVEL
if (!source)
return LUA_ErrInvalid(L, "mobj_t");
if (type >= NUMMOBJTYPES)
@@ -411,6 +579,7 @@ static int lib_pSpawnPlayerMissile(lua_State *L)
mobjtype_t type = luaL_checkinteger(L, 2);
UINT32 flags2 = (UINT32)luaL_optinteger(L, 3, 0);
NOHUD
+ INLEVEL
if (!source)
return LUA_ErrInvalid(L, "mobj_t");
if (type >= NUMMOBJTYPES)
@@ -423,6 +592,7 @@ static int lib_pMobjFlip(lua_State *L)
{
mobj_t *mobj = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
//HUDSAFE
+ INLEVEL
if (!mobj)
return LUA_ErrInvalid(L, "mobj_t");
lua_pushinteger(L, P_MobjFlip(mobj));
@@ -433,6 +603,7 @@ static int lib_pGetMobjGravity(lua_State *L)
{
mobj_t *mobj = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
//HUDSAFE
+ INLEVEL
if (!mobj)
return LUA_ErrInvalid(L, "mobj_t");
lua_pushfixed(L, P_GetMobjGravity(mobj));
@@ -455,6 +626,7 @@ static int lib_pFlashPal(lua_State *L)
UINT16 type = (UINT16)luaL_checkinteger(L, 2);
UINT16 duration = (UINT16)luaL_checkinteger(L, 3);
NOHUD
+ INLEVEL
if (!pl)
return LUA_ErrInvalid(L, "player_t");
P_FlashPal(pl, type, duration);
@@ -465,6 +637,7 @@ static int lib_pGetClosestAxis(lua_State *L)
{
mobj_t *source = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
//HUDSAFE
+ INLEVEL
if (!source)
return LUA_ErrInvalid(L, "mobj_t");
LUA_PushUserdata(L, P_GetClosestAxis(source), META_MOBJ);
@@ -483,6 +656,7 @@ static int lib_pSpawnParaloop(lua_State *L)
statenum_t nstate = luaL_optinteger(L, 8, S_NULL);
boolean spawncenter = lua_optboolean(L, 9);
NOHUD
+ INLEVEL
if (type >= NUMMOBJTYPES)
return luaL_error(L, "mobj type %d out of range (0 - %d)", type, NUMMOBJTYPES-1);
if (nstate >= NUMSTATES)
@@ -496,6 +670,7 @@ static int lib_pBossTargetPlayer(lua_State *L)
mobj_t *actor = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
boolean closest = lua_optboolean(L, 2);
NOHUD
+ INLEVEL
if (!actor)
return LUA_ErrInvalid(L, "mobj_t");
lua_pushboolean(L, P_BossTargetPlayer(actor, closest));
@@ -506,6 +681,7 @@ static int lib_pSupermanLook4Players(lua_State *L)
{
mobj_t *actor = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
NOHUD
+ INLEVEL
if (!actor)
return LUA_ErrInvalid(L, "mobj_t");
lua_pushboolean(L, P_SupermanLook4Players(actor));
@@ -517,6 +693,7 @@ static int lib_pSetScale(lua_State *L)
mobj_t *mobj = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
fixed_t newscale = luaL_checkfixed(L, 2);
NOHUD
+ INLEVEL
if (!mobj)
return LUA_ErrInvalid(L, "mobj_t");
if (newscale < FRACUNIT/100)
@@ -530,6 +707,7 @@ static int lib_pInsideANonSolidFFloor(lua_State *L)
mobj_t *mobj = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
ffloor_t *rover = *((ffloor_t **)luaL_checkudata(L, 2, META_FFLOOR));
//HUDSAFE
+ INLEVEL
if (!mobj)
return LUA_ErrInvalid(L, "mobj_t");
if (!rover)
@@ -542,6 +720,7 @@ static int lib_pCheckDeathPitCollide(lua_State *L)
{
mobj_t *mo = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
//HUDSAFE
+ INLEVEL
if (!mo)
return LUA_ErrInvalid(L, "mobj_t");
lua_pushboolean(L, P_CheckDeathPitCollide(mo));
@@ -553,6 +732,7 @@ static int lib_pCheckSolidLava(lua_State *L)
mobj_t *mo = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
ffloor_t *rover = *((ffloor_t **)luaL_checkudata(L, 2, META_FFLOOR));
//HUDSAFE
+ INLEVEL
if (!mo)
return LUA_ErrInvalid(L, "mobj_t");
if (!rover)
@@ -566,6 +746,7 @@ static int lib_pCanRunOnWater(lua_State *L)
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
ffloor_t *rover = *((ffloor_t **)luaL_checkudata(L, 2, META_FFLOOR));
//HUDSAFE
+ INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
if (!rover)
@@ -581,6 +762,7 @@ static int lib_pGetPlayerHeight(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
//HUDSAFE
+ INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
lua_pushfixed(L, P_GetPlayerHeight(player));
@@ -591,6 +773,7 @@ static int lib_pGetPlayerSpinHeight(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
//HUDSAFE
+ INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
lua_pushfixed(L, P_GetPlayerSpinHeight(player));
@@ -601,6 +784,7 @@ static int lib_pGetPlayerControlDirection(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
//HUDSAFE
+ INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
lua_pushinteger(L, P_GetPlayerControlDirection(player));
@@ -612,16 +796,41 @@ static int lib_pAddPlayerScore(lua_State *L)
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
UINT32 amount = (UINT32)luaL_checkinteger(L, 2);
NOHUD
+ INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
P_AddPlayerScore(player, amount);
return 0;
}
+static int lib_pStealPlayerScore(lua_State *L)
+{
+ player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
+ UINT32 amount = (UINT32)luaL_checkinteger(L, 2);
+ NOHUD
+ INLEVEL
+ if (!player)
+ return LUA_ErrInvalid(L, "player_t");
+ P_StealPlayerScore(player, amount);
+ return 0;
+}
+
+static int lib_pGetJumpFlags(lua_State *L)
+{
+ player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
+ NOHUD
+ INLEVEL
+ if (!player)
+ return LUA_ErrInvalid(L, "player_t");
+ lua_pushinteger(L, P_GetJumpFlags(player));
+ return 1;
+}
+
static int lib_pPlayerInPain(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
//HUDSAFE
+ INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
lua_pushboolean(L, P_PlayerInPain(player));
@@ -633,6 +842,7 @@ static int lib_pDoPlayerPain(lua_State *L)
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
mobj_t *source = NULL, *inflictor = NULL;
NOHUD
+ INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
if (!lua_isnone(L, 2) && lua_isuserdata(L, 2))
@@ -647,6 +857,7 @@ static int lib_pResetPlayer(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
NOHUD
+ INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
P_ResetPlayer(player);
@@ -657,6 +868,7 @@ static int lib_pIsObjectInGoop(lua_State *L)
{
mobj_t *mo = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
//HUDSAFE
+ INLEVEL
if (!mo)
return LUA_ErrInvalid(L, "mobj_t");
lua_pushboolean(L, P_IsObjectInGoop(mo));
@@ -667,6 +879,7 @@ static int lib_pIsObjectOnGround(lua_State *L)
{
mobj_t *mo = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
//HUDSAFE
+ INLEVEL
if (!mo)
return LUA_ErrInvalid(L, "mobj_t");
lua_pushboolean(L, P_IsObjectOnGround(mo));
@@ -677,6 +890,7 @@ static int lib_pInSpaceSector(lua_State *L)
{
mobj_t *mo = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
//HUDSAFE
+ INLEVEL
if (!mo)
return LUA_ErrInvalid(L, "mobj_t");
lua_pushboolean(L, P_InSpaceSector(mo));
@@ -687,6 +901,7 @@ static int lib_pInQuicksand(lua_State *L)
{
mobj_t *mo = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
//HUDSAFE
+ INLEVEL
if (!mo)
return LUA_ErrInvalid(L, "mobj_t");
lua_pushboolean(L, P_InQuicksand(mo));
@@ -699,6 +914,7 @@ static int lib_pSetObjectMomZ(lua_State *L)
fixed_t value = luaL_checkfixed(L, 2);
boolean relative = lua_optboolean(L, 3);
NOHUD
+ INLEVEL
if (!mo)
return LUA_ErrInvalid(L, "mobj_t");
P_SetObjectMomZ(mo, value, relative);
@@ -709,6 +925,7 @@ static int lib_pRestoreMusic(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
NOHUD
+ INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
P_RestoreMusic(player);
@@ -719,6 +936,7 @@ static int lib_pSpawnShieldOrb(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
NOHUD
+ INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
P_SpawnShieldOrb(player);
@@ -729,6 +947,7 @@ static int lib_pSpawnGhostMobj(lua_State *L)
{
mobj_t *mobj = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
NOHUD
+ INLEVEL
if (!mobj)
return LUA_ErrInvalid(L, "mobj_t");
LUA_PushUserdata(L, P_SpawnGhostMobj(mobj), META_MOBJ);
@@ -740,6 +959,7 @@ static int lib_pGivePlayerRings(lua_State *L)
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
INT32 num_rings = (INT32)luaL_checkinteger(L, 2);
NOHUD
+ INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
P_GivePlayerRings(player, num_rings);
@@ -751,6 +971,7 @@ static int lib_pGivePlayerLives(lua_State *L)
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
INT32 numlives = (INT32)luaL_checkinteger(L, 2);
NOHUD
+ INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
P_GivePlayerLives(player, numlives);
@@ -761,6 +982,7 @@ static int lib_pResetScore(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
NOHUD
+ INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
P_ResetScore(player);
@@ -771,29 +993,44 @@ static int lib_pDoJumpShield(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
NOHUD
+ INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
P_DoJumpShield(player);
return 0;
}
+static int lib_pDoBubbleBounce(lua_State *L)
+{
+ player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
+ NOHUD
+ INLEVEL
+ if (!player)
+ return LUA_ErrInvalid(L, "player_t");
+ P_DoBubbleBounce(player);
+ return 0;
+}
+
static int lib_pBlackOw(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
NOHUD
+ INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
P_BlackOw(player);
return 0;
}
-static int lib_pElementalFireTrail(lua_State *L)
+static int lib_pElementalFire(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
+ boolean cropcircle = lua_optboolean(L, 2);
NOHUD
+ INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
- P_ElementalFireTrail(player);
+ P_ElementalFire(player, cropcircle);
return 0;
}
@@ -801,6 +1038,7 @@ static int lib_pDoPlayerExit(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
NOHUD
+ INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
P_DoPlayerExit(player);
@@ -813,6 +1051,7 @@ static int lib_pInstaThrust(lua_State *L)
angle_t angle = luaL_checkangle(L, 2);
fixed_t move = luaL_checkfixed(L, 3);
NOHUD
+ INLEVEL
if (!mo)
return LUA_ErrInvalid(L, "mobj_t");
P_InstaThrust(mo, angle, move);
@@ -848,10 +1087,13 @@ static int lib_pReturnThrustY(lua_State *L)
static int lib_pLookForEnemies(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
+ boolean nonenemies = lua_opttrueboolean(L, 2);
+ boolean bullet = lua_optboolean(L, 3);
NOHUD
+ INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
- lua_pushboolean(L, P_LookForEnemies(player));
+ LUA_PushUserdata(L, P_LookForEnemies(player, nonenemies, bullet), META_MOBJ);
return 1;
}
@@ -861,6 +1103,7 @@ static int lib_pNukeEnemies(lua_State *L)
mobj_t *source = *((mobj_t **)luaL_checkudata(L, 2, META_MOBJ));
fixed_t radius = luaL_checkfixed(L, 3);
NOHUD
+ INLEVEL
if (!inflictor || !source)
return LUA_ErrInvalid(L, "mobj_t");
P_NukeEnemies(inflictor, source, radius);
@@ -872,6 +1115,7 @@ static int lib_pHomingAttack(lua_State *L)
mobj_t *source = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
mobj_t *enemy = *((mobj_t **)luaL_checkudata(L, 2, META_MOBJ));
NOHUD
+ INLEVEL
if (!source || !enemy)
return LUA_ErrInvalid(L, "mobj_t");
P_HomingAttack(source, enemy);
@@ -882,6 +1126,7 @@ static int lib_pSuperReady(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
//HUDSAFE
+ INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
lua_pushboolean(L, P_SuperReady(player));
@@ -893,6 +1138,7 @@ static int lib_pDoJump(lua_State *L)
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
boolean soundandstate = (boolean)lua_opttrueboolean(L, 2);
NOHUD
+ INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
P_DoJump(player, soundandstate);
@@ -903,6 +1149,7 @@ static int lib_pSpawnThokMobj(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
NOHUD
+ INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
P_SpawnThokMobj(player);
@@ -914,6 +1161,7 @@ static int lib_pSpawnSpinMobj(lua_State *L)
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
mobjtype_t type = luaL_checkinteger(L, 2);
NOHUD
+ INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
if (type >= NUMMOBJTYPES)
@@ -928,6 +1176,7 @@ static int lib_pTelekinesis(lua_State *L)
fixed_t thrust = luaL_checkfixed(L, 2);
fixed_t range = luaL_checkfixed(L, 3);
NOHUD
+ INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
P_Telekinesis(player, thrust, range);
@@ -944,6 +1193,7 @@ static int lib_pCheckPosition(lua_State *L)
fixed_t x = luaL_checkfixed(L, 2);
fixed_t y = luaL_checkfixed(L, 3);
NOHUD
+ INLEVEL
if (!thing)
return LUA_ErrInvalid(L, "mobj_t");
lua_pushboolean(L, P_CheckPosition(thing, x, y));
@@ -960,6 +1210,7 @@ static int lib_pTryMove(lua_State *L)
fixed_t y = luaL_checkfixed(L, 3);
boolean allowdropoff = lua_optboolean(L, 4);
NOHUD
+ INLEVEL
if (!thing)
return LUA_ErrInvalid(L, "mobj_t");
lua_pushboolean(L, P_TryMove(thing, x, y, allowdropoff));
@@ -974,6 +1225,7 @@ static int lib_pMove(lua_State *L)
mobj_t *actor = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
fixed_t speed = luaL_checkfixed(L, 2);
NOHUD
+ INLEVEL
if (!actor)
return LUA_ErrInvalid(L, "mobj_t");
lua_pushboolean(L, P_Move(actor, speed));
@@ -990,6 +1242,7 @@ static int lib_pTeleportMove(lua_State *L)
fixed_t y = luaL_checkfixed(L, 3);
fixed_t z = luaL_checkfixed(L, 4);
NOHUD
+ INLEVEL
if (!thing)
return LUA_ErrInvalid(L, "mobj_t");
lua_pushboolean(L, P_TeleportMove(thing, x, y, z));
@@ -1002,6 +1255,7 @@ static int lib_pSlideMove(lua_State *L)
{
mobj_t *mo = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
NOHUD
+ INLEVEL
if (!mo)
return LUA_ErrInvalid(L, "mobj_t");
P_SlideMove(mo);
@@ -1012,6 +1266,7 @@ static int lib_pBounceMove(lua_State *L)
{
mobj_t *mo = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
NOHUD
+ INLEVEL
if (!mo)
return LUA_ErrInvalid(L, "mobj_t");
P_BounceMove(mo);
@@ -1023,6 +1278,7 @@ static int lib_pCheckSight(lua_State *L)
mobj_t *t1 = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
mobj_t *t2 = *((mobj_t **)luaL_checkudata(L, 2, META_MOBJ));
//HUDSAFE?
+ INLEVEL
if (!t1 || !t2)
return LUA_ErrInvalid(L, "mobj_t");
lua_pushboolean(L, P_CheckSight(t1, t2));
@@ -1037,6 +1293,7 @@ static int lib_pCheckHoopPosition(lua_State *L)
fixed_t z = luaL_checkfixed(L, 4);
fixed_t radius = luaL_checkfixed(L, 5);
NOHUD
+ INLEVEL
if (!hoopthing)
return LUA_ErrInvalid(L, "mobj_t");
P_CheckHoopPosition(hoopthing, x, y, z, radius);
@@ -1049,6 +1306,7 @@ static int lib_pRadiusAttack(lua_State *L)
mobj_t *source = *((mobj_t **)luaL_checkudata(L, 2, META_MOBJ));
fixed_t damagedist = luaL_checkfixed(L, 3);
NOHUD
+ INLEVEL
if (!spot || !source)
return LUA_ErrInvalid(L, "mobj_t");
P_RadiusAttack(spot, source, damagedist);
@@ -1062,6 +1320,7 @@ static int lib_pFloorzAtPos(lua_State *L)
fixed_t z = luaL_checkfixed(L, 3);
fixed_t height = luaL_checkfixed(L, 4);
//HUDSAFE
+ INLEVEL
lua_pushfixed(L, P_FloorzAtPos(x, y, z, height));
return 1;
}
@@ -1071,6 +1330,7 @@ static int lib_pDoSpring(lua_State *L)
mobj_t *spring = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
mobj_t *object = *((mobj_t **)luaL_checkudata(L, 2, META_MOBJ));
NOHUD
+ INLEVEL
if (!spring || !object)
return LUA_ErrInvalid(L, "mobj_t");
lua_pushboolean(L, P_DoSpring(spring, object));
@@ -1084,6 +1344,7 @@ static int lib_pRemoveShield(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
NOHUD
+ INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
P_RemoveShield(player);
@@ -1096,6 +1357,7 @@ static int lib_pDamageMobj(lua_State *L)
INT32 damage;
UINT8 damagetype;
NOHUD
+ INLEVEL
if (!target)
return LUA_ErrInvalid(L, "mobj_t");
if (!lua_isnone(L, 2) && lua_isuserdata(L, 2))
@@ -1113,6 +1375,7 @@ static int lib_pKillMobj(lua_State *L)
mobj_t *target = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ)), *inflictor = NULL, *source = NULL;
UINT8 damagetype;
NOHUD
+ INLEVEL
if (!target)
return LUA_ErrInvalid(L, "mobj_t");
if (!lua_isnone(L, 2) && lua_isuserdata(L, 2))
@@ -1129,10 +1392,11 @@ static int lib_pPlayerRingBurst(lua_State *L)
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
INT32 num_rings = (INT32)luaL_optinteger(L, 2, -1);
NOHUD
+ INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
if (num_rings == -1)
- num_rings = player->health - 1;
+ num_rings = player->rings;
P_PlayerRingBurst(player, num_rings);
return 0;
}
@@ -1141,6 +1405,7 @@ static int lib_pPlayerWeaponPanelBurst(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
NOHUD
+ INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
P_PlayerWeaponPanelBurst(player);
@@ -1151,17 +1416,30 @@ static int lib_pPlayerWeaponAmmoBurst(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
NOHUD
+ INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
P_PlayerWeaponAmmoBurst(player);
return 0;
}
+static int lib_pPlayerWeaponPanelOrAmmoBurst(lua_State *L)
+{
+ player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
+ NOHUD
+ INLEVEL
+ if (!player)
+ return LUA_ErrInvalid(L, "player_t");
+ P_PlayerWeaponPanelOrAmmoBurst(player);
+ return 0;
+}
+
static int lib_pPlayerEmeraldBurst(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
boolean toss = lua_optboolean(L, 2);
NOHUD
+ INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
P_PlayerEmeraldBurst(player, toss);
@@ -1173,6 +1451,7 @@ static int lib_pPlayerFlagBurst(lua_State *L)
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
boolean toss = lua_optboolean(L, 2);
NOHUD
+ INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
P_PlayerFlagBurst(player, toss);
@@ -1184,6 +1463,7 @@ static int lib_pPlayRinglossSound(lua_State *L)
mobj_t *source = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
player_t *player = NULL;
NOHUD
+ INLEVEL
if (!source)
return LUA_ErrInvalid(L, "mobj_t");
if (!lua_isnone(L, 2) && lua_isuserdata(L, 2))
@@ -1202,6 +1482,7 @@ static int lib_pPlayDeathSound(lua_State *L)
mobj_t *source = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
player_t *player = NULL;
NOHUD
+ INLEVEL
if (!source)
return LUA_ErrInvalid(L, "mobj_t");
if (!lua_isnone(L, 2) && lua_isuserdata(L, 2))
@@ -1220,6 +1501,7 @@ static int lib_pPlayVictorySound(lua_State *L)
mobj_t *source = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
player_t *player = NULL;
NOHUD
+ INLEVEL
if (!source)
return LUA_ErrInvalid(L, "mobj_t");
if (!lua_isnone(L, 2) && lua_isuserdata(L, 2))
@@ -1237,6 +1519,7 @@ static int lib_pPlayLivesJingle(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
NOHUD
+ INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
P_PlayLivesJingle(player);
@@ -1248,6 +1531,7 @@ static int lib_pCanPickupItem(lua_State *L)
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
boolean weapon = lua_optboolean(L, 2);
//HUDSAFE
+ INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
lua_pushboolean(L, P_CanPickupItem(player, weapon));
@@ -1258,12 +1542,24 @@ static int lib_pDoNightsScore(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
NOHUD
+ INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
P_DoNightsScore(player);
return 0;
}
+static int lib_pDoMatchSuper(lua_State *L)
+{
+ player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
+ NOHUD
+ INLEVEL
+ if (!player)
+ return LUA_ErrInvalid(L, "player_t");
+ P_DoMatchSuper(player);
+ return 0;
+}
+
// P_SPEC
////////////
@@ -1273,6 +1569,7 @@ static int lib_pThrust(lua_State *L)
angle_t angle = luaL_checkangle(L, 2);
fixed_t move = luaL_checkfixed(L, 3);
NOHUD
+ INLEVEL
if (!mo)
return LUA_ErrInvalid(L, "mobj_t");
P_Thrust(mo, angle, move);
@@ -1284,6 +1581,7 @@ static int lib_pSetMobjStateNF(lua_State *L)
mobj_t *mobj = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
statenum_t state = luaL_checkinteger(L, 2);
NOHUD
+ INLEVEL
if (!mobj)
return LUA_ErrInvalid(L, "mobj_t");
if (state >= NUMSTATES)
@@ -1299,6 +1597,7 @@ static int lib_pDoSuperTransformation(lua_State *L)
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
boolean giverings = lua_optboolean(L, 2);
NOHUD
+ INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
P_DoSuperTransformation(player, giverings);
@@ -1309,6 +1608,7 @@ static int lib_pExplodeMissile(lua_State *L)
{
mobj_t *mo = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
NOHUD
+ INLEVEL
if (!mo)
return LUA_ErrInvalid(L, "mobj_t");
P_ExplodeMissile(mo);
@@ -1321,6 +1621,7 @@ static int lib_pPlayerTouchingSectorSpecial(lua_State *L)
INT32 section = (INT32)luaL_checkinteger(L, 2);
INT32 number = (INT32)luaL_checkinteger(L, 3);
//HUDSAFE
+ INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
LUA_PushUserdata(L, P_PlayerTouchingSectorSpecial(player, section, number), META_SECTOR);
@@ -1333,6 +1634,7 @@ static int lib_pFindSpecialLineFromTag(lua_State *L)
INT16 line = (INT16)luaL_checkinteger(L, 2);
INT32 start = (INT32)luaL_optinteger(L, 3, -1);
NOHUD
+ INLEVEL
lua_pushinteger(L, P_FindSpecialLineFromTag(special, line, start));
return 1;
}
@@ -1342,6 +1644,7 @@ static int lib_pSwitchWeather(lua_State *L)
INT32 weathernum = (INT32)luaL_checkinteger(L, 1);
player_t *user = NULL;
NOHUD
+ INLEVEL
if (!lua_isnone(L, 2) && lua_isuserdata(L, 2)) // if a player, setup weather for only the player, otherwise setup weather for all players
user = *((player_t **)luaL_checkudata(L, 2, META_PLAYER));
if (!user) // global
@@ -1357,6 +1660,7 @@ static int lib_pLinedefExecute(lua_State *L)
mobj_t *actor = NULL;
sector_t *caller = NULL;
NOHUD
+ INLEVEL
if (!lua_isnone(L, 2) && lua_isuserdata(L, 2))
actor = *((mobj_t **)luaL_checkudata(L, 2, META_MOBJ));
if (!lua_isnone(L, 3) && lua_isuserdata(L, 3))
@@ -1369,6 +1673,7 @@ static int lib_pSpawnLightningFlash(lua_State *L)
{
sector_t *sector = *((sector_t **)luaL_checkudata(L, 1, META_SECTOR));
NOHUD
+ INLEVEL
if (!sector)
return LUA_ErrInvalid(L, "sector_t");
P_SpawnLightningFlash(sector);
@@ -1381,6 +1686,7 @@ static int lib_pFadeLight(lua_State *L)
INT32 destvalue = (INT32)luaL_checkinteger(L, 2);
INT32 speed = (INT32)luaL_checkinteger(L, 3);
NOHUD
+ INLEVEL
P_FadeLight(tag, destvalue, speed);
return 0;
}
@@ -1389,6 +1695,7 @@ static int lib_pThingOnSpecial3DFloor(lua_State *L)
{
mobj_t *mo = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
NOHUD
+ INLEVEL
if (!mo)
return LUA_ErrInvalid(L, "mobj_t");
LUA_PushUserdata(L, P_ThingOnSpecial3DFloor(mo), META_SECTOR);
@@ -1399,6 +1706,7 @@ static int lib_pIsFlagAtBase(lua_State *L)
{
mobjtype_t flag = luaL_checkinteger(L, 1);
NOHUD
+ INLEVEL
if (flag >= NUMMOBJTYPES)
return luaL_error(L, "mobj type %d out of range (0 - %d)", flag, NUMMOBJTYPES-1);
lua_pushboolean(L, P_IsFlagAtBase(flag));
@@ -1410,6 +1718,7 @@ static int lib_pSetupLevelSky(lua_State *L)
INT32 skynum = (INT32)luaL_checkinteger(L, 1);
player_t *user = NULL;
NOHUD
+ INLEVEL
if (!lua_isnone(L, 2) && lua_isuserdata(L, 2)) // if a player, setup sky for only the player, otherwise setup sky for all players
user = *((player_t **)luaL_checkudata(L, 2, META_PLAYER));
if (!user) // global
@@ -1428,6 +1737,7 @@ static int lib_pSetSkyboxMobj(lua_State *L)
int w = 0;
NOHUD
+ INLEVEL
if (!lua_isnil(L,1)) // nil leaves mo as NULL to remove the skybox rendering.
{
mo = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ)); // otherwise it is a skybox mobj.
@@ -1474,6 +1784,7 @@ static int lib_pStartQuake(lua_State *L)
static mappoint_t q_epicenter = {0,0,0};
NOHUD
+ INLEVEL
// While technically we don't support epicenter and radius,
// we get their values anyway if they exist.
@@ -1535,6 +1846,7 @@ static int lib_evCrumbleChain(lua_State *L)
sector_t *sec = *((sector_t **)luaL_checkudata(L, 1, META_SECTOR));
ffloor_t *rover = *((ffloor_t **)luaL_checkudata(L, 2, META_FFLOOR));
NOHUD
+ INLEVEL
if (!sec)
return LUA_ErrInvalid(L, "sector_t");
if (!rover)
@@ -1543,6 +1855,33 @@ static int lib_evCrumbleChain(lua_State *L)
return 0;
}
+static int lib_evStartCrumble(lua_State *L)
+{
+ sector_t *sec = *((sector_t **)luaL_checkudata(L, 1, META_SECTOR));
+ ffloor_t *rover = *((ffloor_t **)luaL_checkudata(L, 2, META_FFLOOR));
+ boolean floating = lua_optboolean(L, 3);
+ player_t *player = NULL;
+ fixed_t origalpha;
+ boolean crumblereturn = lua_optboolean(L, 6);
+ NOHUD
+ if (!sec)
+ return LUA_ErrInvalid(L, "sector_t");
+ if (!rover)
+ return LUA_ErrInvalid(L, "ffloor_t");
+ if (!lua_isnone(L, 4) && lua_isuserdata(L, 4))
+ {
+ player = *((player_t **)luaL_checkudata(L, 4, META_PLAYER));
+ if (!player)
+ return LUA_ErrInvalid(L, "player_t");
+ }
+ if (!lua_isnone(L,5))
+ origalpha = luaL_checkfixed(L, 5);
+ else
+ origalpha = rover->alpha;
+ lua_pushboolean(L, EV_StartCrumble(sec, rover, floating, player, origalpha, crumblereturn) != 0);
+ return 0;
+}
+
// R_DEFS
////////////
@@ -1591,6 +1930,7 @@ static int lib_rPointInSubsector(lua_State *L)
fixed_t x = luaL_checkfixed(L, 1);
fixed_t y = luaL_checkfixed(L, 2);
//HUDSAFE
+ INLEVEL
LUA_PushUserdata(L, R_PointInSubsector(x, y), META_SUBSECTOR);
return 1;
}
@@ -1625,6 +1965,7 @@ static int lib_rSetPlayerSkin(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
NOHUD
+ INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
if (lua_isnoneornil(L, 2))
@@ -1809,6 +2150,7 @@ static int lib_sOriginPlaying(lua_State *L)
{
void *origin = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
NOHUD
+ INLEVEL
if (!origin)
return LUA_ErrInvalid(L, "mobj_t");
lua_pushboolean(L, S_OriginPlaying(origin));
@@ -1830,6 +2172,7 @@ static int lib_sSoundPlaying(lua_State *L)
void *origin = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
sfxenum_t id = luaL_checkinteger(L, 2);
NOHUD
+ INLEVEL
if (!origin)
return LUA_ErrInvalid(L, "mobj_t");
if (id >= NUMSFX)
@@ -1838,6 +2181,31 @@ static int lib_sSoundPlaying(lua_State *L)
return 1;
}
+// This doesn't really exist, but we're providing it as a handy netgame-safe wrapper for stuff that should be locally handled.
+
+static int lib_sStartMusicCaption(lua_State *L)
+{
+ player_t *player = NULL;
+ const char *caption = luaL_checkstring(L, 1);
+ UINT16 lifespan = (UINT16)luaL_checkinteger(L, 2);
+ //HUDSAFE
+ INLEVEL
+
+ if (!lua_isnone(L, 3) && lua_isuserdata(L, 3))
+ {
+ player = *((player_t **)luaL_checkudata(L, 3, META_PLAYER));
+ if (!player)
+ return LUA_ErrInvalid(L, "player_t");
+ }
+
+ if (lifespan && (!player || P_IsLocalPlayer(player)))
+ {
+ strlcpy(S_sfx[sfx_None].caption, caption, sizeof(S_sfx[sfx_None].caption));
+ S_StartCaption(sfx_None, -1, lifespan);
+ }
+ return 0;
+}
+
// G_GAME
////////////
@@ -1845,6 +2213,7 @@ static int lib_gBuildMapName(lua_State *L)
{
INT32 map = luaL_optinteger(L, 1, gamemap);
//HUDSAFE
+ INLEVEL
lua_pushstring(L, G_BuildMapName(map));
return 1;
}
@@ -1853,6 +2222,7 @@ static int lib_gDoReborn(lua_State *L)
{
INT32 playernum = luaL_checkinteger(L, 1);
NOHUD
+ INLEVEL
if (playernum >= MAXPLAYERS)
return luaL_error(L, "playernum %d out of range (0 - %d)", playernum, MAXPLAYERS-1);
G_DoReborn(playernum);
@@ -1863,6 +2233,7 @@ static int lib_gExitLevel(lua_State *L)
{
int n = lua_gettop(L); // Num arguments
NOHUD
+ INLEVEL
// LUA EXTENSION: Custom exit like support
// Supported:
@@ -1889,6 +2260,7 @@ static int lib_gIsSpecialStage(lua_State *L)
{
INT32 mapnum = luaL_optinteger(L, 1, gamemap);
//HUDSAFE
+ INLEVEL
lua_pushboolean(L, G_IsSpecialStage(mapnum));
return 1;
}
@@ -1896,6 +2268,7 @@ static int lib_gIsSpecialStage(lua_State *L)
static int lib_gGametypeUsesLives(lua_State *L)
{
//HUDSAFE
+ INLEVEL
lua_pushboolean(L, G_GametypeUsesLives());
return 1;
}
@@ -1903,6 +2276,7 @@ static int lib_gGametypeUsesLives(lua_State *L)
static int lib_gGametypeHasTeams(lua_State *L)
{
//HUDSAFE
+ INLEVEL
lua_pushboolean(L, G_GametypeHasTeams());
return 1;
}
@@ -1910,6 +2284,7 @@ static int lib_gGametypeHasTeams(lua_State *L)
static int lib_gGametypeHasSpectators(lua_State *L)
{
//HUDSAFE
+ INLEVEL
lua_pushboolean(L, G_GametypeHasSpectators());
return 1;
}
@@ -1917,6 +2292,7 @@ static int lib_gGametypeHasSpectators(lua_State *L)
static int lib_gRingSlingerGametype(lua_State *L)
{
//HUDSAFE
+ INLEVEL
lua_pushboolean(L, G_RingSlingerGametype());
return 1;
}
@@ -1924,6 +2300,7 @@ static int lib_gRingSlingerGametype(lua_State *L)
static int lib_gPlatformGametype(lua_State *L)
{
//HUDSAFE
+ INLEVEL
lua_pushboolean(L, G_PlatformGametype());
return 1;
}
@@ -1931,6 +2308,7 @@ static int lib_gPlatformGametype(lua_State *L)
static int lib_gTagGametype(lua_State *L)
{
//HUDSAFE
+ INLEVEL
lua_pushboolean(L, G_TagGametype());
return 1;
}
@@ -1978,6 +2356,7 @@ static int lib_gTicsToMilliseconds(lua_State *L)
static luaL_Reg lib[] = {
{"print", lib_print},
+ {"userdataType", lib_userdataType},
// m_random
{"P_RandomFixed",lib_pRandomFixed},
@@ -1991,6 +2370,7 @@ static luaL_Reg lib[] = {
// p_maputil
{"P_AproxDistance",lib_pAproxDistance},
{"P_ClosestPointOnLine",lib_pClosestPointOnLine},
+ {"P_PointOnLineSide",lib_pPointOnLineSide},
// p_enemy
{"P_CheckMeleeRange", lib_pCheckMeleeRange},
@@ -2005,6 +2385,8 @@ static luaL_Reg lib[] = {
// don't add P_SetMobjState or P_SetPlayerMobjState, use "mobj.state = S_NEWSTATE" instead.
{"P_SpawnMobj",lib_pSpawnMobj},
{"P_RemoveMobj",lib_pRemoveMobj},
+ {"P_IsValidSprite2", lib_pIsValidSprite2},
+ {"P_SpawnLockOn", lib_pSpawnLockOn},
{"P_SpawnMissile",lib_pSpawnMissile},
{"P_SpawnXYZMissile",lib_pSpawnXYZMissile},
{"P_SpawnPointMissile",lib_pSpawnPointMissile},
@@ -2031,6 +2413,8 @@ static luaL_Reg lib[] = {
{"P_GetPlayerSpinHeight",lib_pGetPlayerSpinHeight},
{"P_GetPlayerControlDirection",lib_pGetPlayerControlDirection},
{"P_AddPlayerScore",lib_pAddPlayerScore},
+ {"P_StealPlayerScore",lib_pStealPlayerScore},
+ {"P_GetJumpFlags",lib_pGetJumpFlags},
{"P_PlayerInPain",lib_pPlayerInPain},
{"P_DoPlayerPain",lib_pDoPlayerPain},
{"P_ResetPlayer",lib_pResetPlayer},
@@ -2046,8 +2430,9 @@ static luaL_Reg lib[] = {
{"P_GivePlayerLives",lib_pGivePlayerLives},
{"P_ResetScore",lib_pResetScore},
{"P_DoJumpShield",lib_pDoJumpShield},
+ {"P_DoBubbleBounce",lib_pDoBubbleBounce},
{"P_BlackOw",lib_pBlackOw},
- {"P_ElementalFireTrail",lib_pElementalFireTrail},
+ {"P_ElementalFire",lib_pElementalFire},
{"P_DoPlayerExit",lib_pDoPlayerExit},
{"P_InstaThrust",lib_pInstaThrust},
{"P_ReturnThrustX",lib_pReturnThrustX},
@@ -2081,6 +2466,7 @@ static luaL_Reg lib[] = {
{"P_PlayerRingBurst",lib_pPlayerRingBurst},
{"P_PlayerWeaponPanelBurst",lib_pPlayerWeaponPanelBurst},
{"P_PlayerWeaponAmmoBurst",lib_pPlayerWeaponAmmoBurst},
+ {"P_PlayerWeaponPanelOrAmmoBurst", lib_pPlayerWeaponPanelOrAmmoBurst},
{"P_PlayerEmeraldBurst",lib_pPlayerEmeraldBurst},
{"P_PlayerFlagBurst",lib_pPlayerFlagBurst},
{"P_PlayRinglossSound",lib_pPlayRinglossSound},
@@ -2089,6 +2475,7 @@ static luaL_Reg lib[] = {
{"P_PlayLivesJingle",lib_pPlayLivesJingle},
{"P_CanPickupItem",lib_pCanPickupItem},
{"P_DoNightsScore",lib_pDoNightsScore},
+ {"P_DoMatchSuper",lib_pDoMatchSuper},
// p_spec
{"P_Thrust",lib_pThrust},
@@ -2107,6 +2494,7 @@ static luaL_Reg lib[] = {
{"P_SetSkyboxMobj",lib_pSetSkyboxMobj},
{"P_StartQuake",lib_pStartQuake},
{"EV_CrumbleChain",lib_evCrumbleChain},
+ {"EV_StartCrumble",lib_evStartCrumble},
// r_defs
{"R_PointToAngle",lib_rPointToAngle},
@@ -2130,6 +2518,7 @@ static luaL_Reg lib[] = {
{"S_OriginPlaying",lib_sOriginPlaying},
{"S_IdPlaying",lib_sIdPlaying},
{"S_SoundPlaying",lib_sSoundPlaying},
+ {"S_StartMusicCaption", lib_sStartMusicCaption},
// g_game
{"G_BuildMapName",lib_gBuildMapName},
diff --git a/src/lua_blockmaplib.c b/src/lua_blockmaplib.c
new file mode 100644
index 000000000..ccd90f993
--- /dev/null
+++ b/src/lua_blockmaplib.c
@@ -0,0 +1,269 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 2016 by Iestyn "Monster Iestyn" Jealous.
+// Copyright (C) 2016 by Sonic Team Junior.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+/// \file lua_blockmaplib.c
+/// \brief blockmap library for Lua scripting
+
+#include "doomdef.h"
+#ifdef HAVE_BLUA
+#include "p_local.h"
+#include "r_main.h" // validcount
+#include "lua_script.h"
+#include "lua_libs.h"
+//#include "lua_hud.h" // hud_running errors
+
+static const char *const search_opt[] = {
+ "objects",
+ "lines",
+ NULL};
+
+// a quickly-made function pointer typedef used by lib_searchBlockmap...
+// return values:
+// 0 - normal, no interruptions
+// 1 - stop search through current block
+// 2 - stop search completely
+typedef UINT8 (*blockmap_func)(lua_State *, INT32, INT32, mobj_t *);
+
+static boolean blockfuncerror = false; // errors should only print once per search blockmap call
+
+// Helper function for "objects" search
+static UINT8 lib_searchBlockmap_Objects(lua_State *L, INT32 x, INT32 y, mobj_t *thing)
+{
+ mobj_t *mobj, *bnext = NULL;
+
+ if (x < 0 || y < 0 || x >= bmapwidth || y >= bmapheight)
+ return 0;
+
+ // Check interaction with the objects in the blockmap.
+ for (mobj = blocklinks[y*bmapwidth + x]; mobj; mobj = bnext)
+ {
+ P_SetTarget(&bnext, mobj->bnext); // We want to note our reference to bnext here incase it is MF_NOTHINK and gets removed!
+ if (mobj == thing)
+ continue; // our thing just found itself, so move on
+ lua_pushvalue(L, 1); // push function
+ LUA_PushUserdata(L, thing, META_MOBJ);
+ LUA_PushUserdata(L, mobj, META_MOBJ);
+ if (lua_pcall(gL, 2, 1, 0)) {
+ if (!blockfuncerror || cv_debug & DBG_LUA)
+ CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+ lua_pop(gL, 1);
+ blockfuncerror = true;
+ return 0; // *shrugs*
+ }
+ if (!lua_isnil(gL, -1))
+ { // if nil, continue
+ if (lua_toboolean(gL, -1))
+ return 2; // stop whole search
+ else
+ return 1; // stop block search
+ }
+ lua_pop(gL, 1);
+ if (P_MobjWasRemoved(thing) // func just popped our thing, cannot continue.
+ || (bnext && P_MobjWasRemoved(bnext))) // func just broke blockmap chain, cannot continue.
+ {
+ P_SetTarget(&bnext, NULL);
+ return (P_MobjWasRemoved(thing)) ? 2 : 1;
+ }
+ }
+ return 0;
+}
+
+// Helper function for "lines" search
+static UINT8 lib_searchBlockmap_Lines(lua_State *L, INT32 x, INT32 y, mobj_t *thing)
+{
+ INT32 offset;
+ const INT32 *list; // Big blockmap
+#ifdef POLYOBJECTS
+ polymaplink_t *plink; // haleyjd 02/22/06
+#endif
+ line_t *ld;
+
+ if (x < 0 || y < 0 || x >= bmapwidth || y >= bmapheight)
+ return 0;
+
+ offset = y*bmapwidth + x;
+
+#ifdef POLYOBJECTS
+ // haleyjd 02/22/06: consider polyobject lines
+ plink = polyblocklinks[offset];
+
+ while (plink)
+ {
+ polyobj_t *po = plink->po;
+
+ if (po->validcount != validcount) // if polyobj hasn't been checked
+ {
+ size_t i;
+ po->validcount = validcount;
+
+ for (i = 0; i < po->numLines; ++i)
+ {
+ if (po->lines[i]->validcount == validcount) // line has been checked
+ continue;
+ po->lines[i]->validcount = validcount;
+
+ lua_pushvalue(L, 1);
+ LUA_PushUserdata(L, thing, META_MOBJ);
+ LUA_PushUserdata(L, po->lines[i], META_LINE);
+ if (lua_pcall(gL, 2, 1, 0)) {
+ if (!blockfuncerror || cv_debug & DBG_LUA)
+ CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+ lua_pop(gL, 1);
+ blockfuncerror = true;
+ return 0; // *shrugs*
+ }
+ if (!lua_isnil(gL, -1))
+ { // if nil, continue
+ if (lua_toboolean(gL, -1))
+ return 2; // stop whole search
+ else
+ return 1; // stop block search
+ }
+ lua_pop(gL, 1);
+ if (P_MobjWasRemoved(thing))
+ return 2;
+ }
+ }
+ plink = (polymaplink_t *)(plink->link.next);
+ }
+#endif
+
+ offset = *(blockmap + offset); // offset = blockmap[y*bmapwidth+x];
+
+ // First index is really empty, so +1 it.
+ for (list = blockmaplump + offset + 1; *list != -1; list++)
+ {
+ ld = &lines[*list];
+
+ if (ld->validcount == validcount)
+ continue; // Line has already been checked.
+
+ ld->validcount = validcount;
+
+ lua_pushvalue(L, 1);
+ LUA_PushUserdata(L, thing, META_MOBJ);
+ LUA_PushUserdata(L, ld, META_LINE);
+ if (lua_pcall(gL, 2, 1, 0)) {
+ if (!blockfuncerror || cv_debug & DBG_LUA)
+ CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+ lua_pop(gL, 1);
+ blockfuncerror = true;
+ return 0; // *shrugs*
+ }
+ if (!lua_isnil(gL, -1))
+ { // if nil, continue
+ if (lua_toboolean(gL, -1))
+ return 2; // stop whole search
+ else
+ return 1; // stop block search
+ }
+ lua_pop(gL, 1);
+ if (P_MobjWasRemoved(thing))
+ return 2;
+ }
+ return 0; // Everything was checked.
+}
+
+// The searchBlockmap function
+// arguments: searchBlockmap(searchtype, function, mobj, [x1, x2, y1, y2])
+// return value:
+// true = search completely uninteruppted,
+// false = searching of at least one block stopped mid-way (including if the whole search was stopped)
+static int lib_searchBlockmap(lua_State *L)
+{
+ int searchtype = luaL_checkoption(L, 1, "objects", search_opt);
+ int n;
+ mobj_t *mobj;
+ INT32 xl, xh, yl, yh, bx, by;
+ fixed_t x1, x2, y1, y2;
+ boolean retval = true;
+ UINT8 funcret = 0;
+ blockmap_func searchFunc;
+
+ if (gamestate != GS_LEVEL)
+ return luaL_error(L, "This function can only be used in a level!");
+
+ lua_remove(L, 1); // remove searchtype, stack is now function, mobj, [x1, x2, y1, y2]
+ luaL_checktype(L, 1, LUA_TFUNCTION);
+
+ switch (searchtype)
+ {
+ case 0: // "objects"
+ default:
+ searchFunc = lib_searchBlockmap_Objects;
+ break;
+ case 1: // "lines"
+ searchFunc = lib_searchBlockmap_Lines;
+ break;
+ }
+
+ // the mobj we are searching around, the "calling" mobj we could say
+ mobj = *((mobj_t **)luaL_checkudata(L, 2, META_MOBJ));
+ if (!mobj)
+ return LUA_ErrInvalid(L, "mobj_t");
+
+ n = lua_gettop(L);
+
+ if (n > 2) // specific x/y ranges have been supplied
+ {
+ if (n < 6)
+ return luaL_error(L, "arguments 4 to 6 not all given (expected 4 fixed-point integers)");
+
+ x1 = luaL_checkfixed(L, 3);
+ x2 = luaL_checkfixed(L, 4);
+ y1 = luaL_checkfixed(L, 5);
+ y2 = luaL_checkfixed(L, 6);
+ }
+ else // mobj and function only - search around mobj's radius by default
+ {
+ fixed_t radius = mobj->radius + MAXRADIUS;
+ x1 = mobj->x - radius;
+ x2 = mobj->x + radius;
+ y1 = mobj->y - radius;
+ y2 = mobj->y + radius;
+ }
+ lua_settop(L, 2); // pop everything except function, mobj
+
+ xl = (unsigned)(x1 - bmaporgx)>>MAPBLOCKSHIFT;
+ xh = (unsigned)(x2 - bmaporgx)>>MAPBLOCKSHIFT;
+ yl = (unsigned)(y1 - bmaporgy)>>MAPBLOCKSHIFT;
+ yh = (unsigned)(y2 - bmaporgy)>>MAPBLOCKSHIFT;
+
+ BMBOUNDFIX(xl, xh, yl, yh);
+
+ blockfuncerror = false; // reset
+ validcount++;
+ for (bx = xl; bx <= xh; bx++)
+ for (by = yl; by <= yh; by++)
+ {
+ funcret = searchFunc(L, bx, by, mobj);
+ // return value of searchFunc determines searchFunc's return value and/or when to stop
+ if (funcret == 2){ // stop whole search
+ lua_pushboolean(L, false); // return false
+ return 1;
+ }
+ else if (funcret == 1) // search was interrupted for this block
+ retval = false; // this changes the return value, but doesn't stop the whole search
+ // else don't do anything, continue as normal
+ if (P_MobjWasRemoved(mobj)){ // ...unless the original object was removed
+ lua_pushboolean(L, false); // in which case we have to stop now regardless
+ return 1;
+ }
+ }
+ lua_pushboolean(L, retval);
+ return 1;
+}
+
+int LUA_BlockmapLib(lua_State *L)
+{
+ lua_register(L, "searchBlockmap", lib_searchBlockmap);
+ return 0;
+}
+
+#endif
diff --git a/src/lua_consolelib.c b/src/lua_consolelib.c
index 28cc91762..322fecb64 100644
--- a/src/lua_consolelib.c
+++ b/src/lua_consolelib.c
@@ -22,7 +22,10 @@
#include "lua_libs.h"
#include "lua_hud.h" // hud_running errors
-#define NOHUD if (hud_running) return luaL_error(L, "HUD rendering code should not call this function!");
+#define NOHUD if (hud_running)\
+return luaL_error(L, "HUD rendering code should not call this function!");
+#define INLEVEL if (gamestate != GS_LEVEL)\
+return luaL_error(L, "This function can only be used in a level!");
static const char *cvname = NULL;
@@ -412,6 +415,7 @@ static int lib_consPrintf(lua_State *L)
if (n < 2)
return luaL_error(L, "CONS_Printf requires at least two arguments: player and text.");
//HUDSAFE
+ INLEVEL
plr = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
if (!plr)
return LUA_ErrInvalid(L, "player_t");
diff --git a/src/lua_hook.h b/src/lua_hook.h
index bed32edac..fe5706f56 100644
--- a/src/lua_hook.h
+++ b/src/lua_hook.h
@@ -43,6 +43,10 @@ enum hook {
hook_PlayerMsg,
hook_HurtMsg,
hook_PlayerSpawn,
+ hook_ShieldSpawn,
+ hook_ShieldSpecial,
+ hook_MobjMoveBlocked,
+ hook_MapThingSpawn,
hook_MAX // last hook
};
@@ -60,11 +64,11 @@ UINT8 LUAh_MobjCollideHook(mobj_t *thing1, mobj_t *thing2, enum hook which);
#define LUAh_MobjMoveCollide(thing1, thing2) LUAh_MobjCollideHook(thing1, thing2, hook_MobjMoveCollide) // Hook for PIT_CheckThing by (tmthing) mobj type
boolean LUAh_TouchSpecial(mobj_t *special, mobj_t *toucher); // Hook for P_TouchSpecialThing by mobj type
#define LUAh_MobjFuse(mo) LUAh_MobjHook(mo, hook_MobjFuse) // Hook for mobj->fuse == 0 by mobj type
-#define LUAh_MobjThinker(mo) LUAh_MobjHook(mo, hook_MobjThinker) // Hook for P_MobjThinker or P_SceneryThinker by mobj type
+boolean LUAh_MobjThinker(mobj_t *mo); // Hook for P_MobjThinker or P_SceneryThinker by mobj type
#define LUAh_BossThinker(mo) LUAh_MobjHook(mo, hook_BossThinker) // Hook for P_GenericBossThinker by mobj type
-UINT8 LUAh_ShouldDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage); // Hook for P_DamageMobj by mobj type (Should mobj take damage?)
-boolean LUAh_MobjDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage); // Hook for P_DamageMobj by mobj type (Mobj actually takes damage!)
-boolean LUAh_MobjDeath(mobj_t *target, mobj_t *inflictor, mobj_t *source); // Hook for P_KillMobj by mobj type
+UINT8 LUAh_ShouldDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage, UINT8 damagetype); // Hook for P_DamageMobj by mobj type (Should mobj take damage?)
+boolean LUAh_MobjDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage, UINT8 damagetype); // Hook for P_DamageMobj by mobj type (Mobj actually takes damage!)
+boolean LUAh_MobjDeath(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damagetype); // Hook for P_KillMobj by mobj type
#define LUAh_BossDeath(mo) LUAh_MobjHook(mo, hook_BossDeath) // Hook for A_BossDeath by mobj type
#define LUAh_MobjRemoved(mo) LUAh_MobjHook(mo, hook_MobjRemoved) // Hook for P_RemoveMobj by mobj type
#define LUAh_JumpSpecial(player) LUAh_PlayerHook(player, hook_JumpSpecial) // Hook for P_DoJumpStuff (Any-jumping)
@@ -75,7 +79,11 @@ boolean LUAh_BotTiccmd(player_t *bot, ticcmd_t *cmd); // Hook for B_BuildTiccmd
boolean LUAh_BotAI(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd); // Hook for B_BuildTailsTiccmd by skin name
boolean LUAh_LinedefExecute(line_t *line, mobj_t *mo, sector_t *sector); // Hook for linedef executors
boolean LUAh_PlayerMsg(int source, int target, int flags, char *msg); // Hook for chat messages
-boolean LUAh_HurtMsg(player_t *player, mobj_t *inflictor, mobj_t *source); // Hook for hurt messages
+boolean LUAh_HurtMsg(player_t *player, mobj_t *inflictor, mobj_t *source, UINT8 damagetype); // Hook for hurt messages
#define LUAh_PlayerSpawn(player) LUAh_PlayerHook(player, hook_PlayerSpawn) // Hook for G_SpawnPlayer
+#define LUAh_ShieldSpawn(player) LUAh_PlayerHook(player, hook_ShieldSpawn) // Hook for P_SpawnShieldOrb
+#define LUAh_ShieldSpecial(player) LUAh_PlayerHook(player, hook_ShieldSpecial) // Hook for shield abilities
+#define LUAh_MobjMoveBlocked(mo) LUAh_MobjHook(mo, hook_MobjMoveBlocked) // Hook for P_XYMovement (when movement is blocked)
+boolean LUAh_MapThingSpawn(mobj_t *mo, mapthing_t *mthing); // Hook for P_SpawnMapThing by mobj type
#endif
diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c
index 1b9652571..04efac070 100644
--- a/src/lua_hooklib.c
+++ b/src/lua_hooklib.c
@@ -54,6 +54,10 @@ const char *const hookNames[hook_MAX+1] = {
"PlayerMsg",
"HurtMsg",
"PlayerSpawn",
+ "ShieldSpawn",
+ "ShieldSpecial",
+ "MobjMoveBlocked",
+ "MapThingSpawn",
NULL
};
@@ -74,12 +78,30 @@ typedef struct hook_s* hook_p;
#define FMT_HOOKID "hook_%d"
+// For each mobj type, a linked list to its thinker and collision hooks.
+// That way, we don't have to iterate through all the hooks.
+// We could do that with all other mobj hooks, but it would probably just be
+// a waste of memory since they are only called occasionally. Probably...
+static hook_p mobjthinkerhooks[NUMMOBJTYPES];
+static hook_p mobjcollidehooks[NUMMOBJTYPES];
+
+// For each mobj type, a linked list for other mobj hooks
+static hook_p mobjhooks[NUMMOBJTYPES];
+
+// A linked list for player hooks
+static hook_p playerhooks;
+
+// A linked list for linedef executor hooks
+static hook_p linedefexecutorhooks;
+
+// For other hooks, a unique linked list
hook_p roothook;
// Takes hook, function, and additional arguments (mobj type to act on, etc.)
static int lib_addHook(lua_State *L)
{
static struct hook_s hook = {NULL, 0, 0, {0}, false};
+ static UINT32 nextid;
hook_p hookp, *lastp;
hook.type = luaL_checkoption(L, 1, NULL, hookNames);
@@ -106,9 +128,12 @@ static int lib_addHook(lua_State *L)
case hook_BossDeath:
case hook_MobjRemoved:
case hook_HurtMsg:
+ case hook_MobjMoveBlocked:
+ case hook_MapThingSpawn:
hook.s.mt = MT_NULL;
if (lua_isnumber(L, 2))
hook.s.mt = lua_tonumber(L, 2);
+ luaL_argcheck(L, hook.s.mt < NUMMOBJTYPES, 2, "invalid mobjtype_t");
break;
case hook_BotAI:
hook.s.skinname = NULL;
@@ -141,18 +166,51 @@ static int lib_addHook(lua_State *L)
hooksAvailable[hook.type/8] |= 1<<(hook.type%8);
- // iterate the hook metadata structs
// set hook.id to the highest id + 1
- // set lastp to the last hook struct's "next" pointer.
- lastp = &roothook;
- hook.id = 0;
- for (hookp = roothook; hookp; hookp = hookp->next)
+ hook.id = nextid++;
+
+ // Special cases for some hook types (see the comments above mobjthinkerhooks declaration)
+ switch(hook.type)
{
- if (hookp->id >= hook.id)
- hook.id = hookp->id+1;
- lastp = &hookp->next;
+ case hook_MobjThinker:
+ lastp = &mobjthinkerhooks[hook.s.mt];
+ break;
+ case hook_MobjCollide:
+ case hook_MobjMoveCollide:
+ lastp = &mobjcollidehooks[hook.s.mt];
+ break;
+ case hook_MobjSpawn:
+ case hook_TouchSpecial:
+ case hook_MobjFuse:
+ case hook_BossThinker:
+ case hook_ShouldDamage:
+ case hook_MobjDamage:
+ case hook_MobjDeath:
+ case hook_BossDeath:
+ case hook_MobjRemoved:
+ case hook_MobjMoveBlocked:
+ case hook_MapThingSpawn:
+ lastp = &mobjhooks[hook.s.mt];
+ break;
+ case hook_JumpSpecial:
+ case hook_AbilitySpecial:
+ case hook_SpinSpecial:
+ case hook_JumpSpinSpecial:
+ case hook_PlayerSpawn:
+ lastp = &playerhooks;
+ break;
+ case hook_LinedefExecute:
+ lastp = &linedefexecutorhooks;
+ break;
+ default:
+ lastp = &roothook;
+ break;
}
+ // iterate the hook metadata structs
+ // set lastp to the last hook struct's "next" pointer.
+ for (hookp = *lastp; hookp; hookp = hookp->next)
+ lastp = &hookp->next;
// allocate a permanent memory struct to stuff hook.
hookp = ZZ_Alloc(sizeof(struct hook_s));
memcpy(hookp, &hook, sizeof(struct hook_s));
@@ -183,9 +241,29 @@ boolean LUAh_MobjHook(mobj_t *mo, enum hook which)
lua_settop(gL, 0);
- for (hookp = roothook; hookp; hookp = hookp->next)
- if (hookp->type == which
- && (hookp->s.mt == MT_NULL || hookp->s.mt == mo->type))
+ // Look for all generic mobj hooks
+ for (hookp = mobjhooks[MT_NULL]; hookp; hookp = hookp->next)
+ if (hookp->type == which)
+ {
+ if (lua_gettop(gL) == 0)
+ LUA_PushUserdata(gL, mo, META_MOBJ);
+ lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+ lua_gettable(gL, LUA_REGISTRYINDEX);
+ lua_pushvalue(gL, -2);
+ if (lua_pcall(gL, 1, 1, 0)) {
+ if (!hookp->error || cv_debug & DBG_LUA)
+ CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+ lua_pop(gL, 1);
+ hookp->error = true;
+ continue;
+ }
+ if (lua_toboolean(gL, -1))
+ hooked = true;
+ lua_pop(gL, 1);
+ }
+
+ for (hookp = mobjhooks[mo->type]; hookp; hookp = hookp->next)
+ if (hookp->type == which)
{
if (lua_gettop(gL) == 0)
LUA_PushUserdata(gL, mo, META_MOBJ);
@@ -217,7 +295,7 @@ boolean LUAh_PlayerHook(player_t *plr, enum hook which)
lua_settop(gL, 0);
- for (hookp = roothook; hookp; hookp = hookp->next)
+ for (hookp = playerhooks; hookp; hookp = hookp->next)
if (hookp->type == which)
{
if (lua_gettop(gL) == 0)
@@ -338,9 +416,38 @@ UINT8 LUAh_MobjCollideHook(mobj_t *thing1, mobj_t *thing2, enum hook which)
lua_settop(gL, 0);
- for (hookp = roothook; hookp; hookp = hookp->next)
- if (hookp->type == which
- && (hookp->s.mt == MT_NULL || hookp->s.mt == thing1->type))
+ // Look for all generic mobj collision hooks
+ for (hookp = mobjcollidehooks[MT_NULL]; hookp; hookp = hookp->next)
+ if (hookp->type == which)
+ {
+ if (lua_gettop(gL) == 0)
+ {
+ LUA_PushUserdata(gL, thing1, META_MOBJ);
+ LUA_PushUserdata(gL, thing2, META_MOBJ);
+ }
+ lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+ lua_gettable(gL, LUA_REGISTRYINDEX);
+ lua_pushvalue(gL, -3);
+ lua_pushvalue(gL, -3);
+ if (lua_pcall(gL, 2, 1, 0)) {
+ if (!hookp->error || cv_debug & DBG_LUA)
+ CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+ lua_pop(gL, 1);
+ hookp->error = true;
+ continue;
+ }
+ if (!lua_isnil(gL, -1))
+ { // if nil, leave shouldCollide = 0.
+ if (lua_toboolean(gL, -1))
+ shouldCollide = 1; // Force yes
+ else
+ shouldCollide = 2; // Force no
+ }
+ lua_pop(gL, 1);
+ }
+
+ for (hookp = mobjcollidehooks[thing1->type]; hookp; hookp = hookp->next)
+ if (hookp->type == which)
{
if (lua_gettop(gL) == 0)
{
@@ -372,6 +479,59 @@ UINT8 LUAh_MobjCollideHook(mobj_t *thing1, mobj_t *thing2, enum hook which)
return shouldCollide;
}
+// Hook for mobj thinkers
+boolean LUAh_MobjThinker(mobj_t *mo)
+{
+ hook_p hookp;
+ boolean hooked = false;
+ if (!gL || !(hooksAvailable[hook_MobjThinker/8] & (1<<(hook_MobjThinker%8))))
+ return false;
+
+ lua_settop(gL, 0);
+
+ // Look for all generic mobj thinker hooks
+ for (hookp = mobjthinkerhooks[MT_NULL]; hookp; hookp = hookp->next)
+ {
+ if (lua_gettop(gL) == 0)
+ LUA_PushUserdata(gL, mo, META_MOBJ);
+ lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+ lua_gettable(gL, LUA_REGISTRYINDEX);
+ lua_pushvalue(gL, -2);
+ if (lua_pcall(gL, 1, 1, 0)) {
+ if (!hookp->error || cv_debug & DBG_LUA)
+ CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+ lua_pop(gL, 1);
+ hookp->error = true;
+ continue;
+ }
+ if (lua_toboolean(gL, -1))
+ hooked = true;
+ lua_pop(gL, 1);
+ }
+
+ for (hookp = mobjthinkerhooks[mo->type]; hookp; hookp = hookp->next)
+ {
+ if (lua_gettop(gL) == 0)
+ LUA_PushUserdata(gL, mo, META_MOBJ);
+ lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+ lua_gettable(gL, LUA_REGISTRYINDEX);
+ lua_pushvalue(gL, -2);
+ if (lua_pcall(gL, 1, 1, 0)) {
+ if (!hookp->error || cv_debug & DBG_LUA)
+ CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+ lua_pop(gL, 1);
+ hookp->error = true;
+ continue;
+ }
+ if (lua_toboolean(gL, -1))
+ hooked = true;
+ lua_pop(gL, 1);
+ }
+
+ lua_settop(gL, 0);
+ return hooked;
+}
+
// Hook for P_TouchSpecialThing by mobj type
boolean LUAh_TouchSpecial(mobj_t *special, mobj_t *toucher)
{
@@ -382,9 +542,33 @@ boolean LUAh_TouchSpecial(mobj_t *special, mobj_t *toucher)
lua_settop(gL, 0);
- for (hookp = roothook; hookp; hookp = hookp->next)
- if (hookp->type == hook_TouchSpecial
- && (hookp->s.mt == MT_NULL || hookp->s.mt == special->type))
+ // Look for all generic touch special hooks
+ for (hookp = mobjhooks[MT_NULL]; hookp; hookp = hookp->next)
+ if (hookp->type == hook_TouchSpecial)
+ {
+ if (lua_gettop(gL) == 0)
+ {
+ LUA_PushUserdata(gL, special, META_MOBJ);
+ LUA_PushUserdata(gL, toucher, META_MOBJ);
+ }
+ lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+ lua_gettable(gL, LUA_REGISTRYINDEX);
+ lua_pushvalue(gL, -3);
+ lua_pushvalue(gL, -3);
+ if (lua_pcall(gL, 2, 1, 0)) {
+ if (!hookp->error || cv_debug & DBG_LUA)
+ CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+ lua_pop(gL, 1);
+ hookp->error = true;
+ continue;
+ }
+ if (lua_toboolean(gL, -1))
+ hooked = true;
+ lua_pop(gL, 1);
+ }
+
+ for (hookp = mobjhooks[special->type]; hookp; hookp = hookp->next)
+ if (hookp->type == hook_TouchSpecial)
{
if (lua_gettop(gL) == 0)
{
@@ -412,7 +596,7 @@ boolean LUAh_TouchSpecial(mobj_t *special, mobj_t *toucher)
}
// Hook for P_DamageMobj by mobj type (Should mobj take damage?)
-UINT8 LUAh_ShouldDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage)
+UINT8 LUAh_ShouldDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage, UINT8 damagetype)
{
hook_p hookp;
UINT8 shouldDamage = 0; // 0 = default, 1 = force yes, 2 = force no.
@@ -421,9 +605,9 @@ UINT8 LUAh_ShouldDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32
lua_settop(gL, 0);
- for (hookp = roothook; hookp; hookp = hookp->next)
- if (hookp->type == hook_ShouldDamage
- && (hookp->s.mt == MT_NULL || hookp->s.mt == target->type))
+ // Look for all generic should damage hooks
+ for (hookp = mobjhooks[MT_NULL]; hookp; hookp = hookp->next)
+ if (hookp->type == hook_ShouldDamage)
{
if (lua_gettop(gL) == 0)
{
@@ -455,12 +639,47 @@ UINT8 LUAh_ShouldDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32
lua_pop(gL, 1);
}
+ for (hookp = mobjhooks[target->type]; hookp; hookp = hookp->next)
+ if (hookp->type == hook_ShouldDamage)
+ {
+ if (lua_gettop(gL) == 0)
+ {
+ LUA_PushUserdata(gL, target, META_MOBJ);
+ LUA_PushUserdata(gL, inflictor, META_MOBJ);
+ LUA_PushUserdata(gL, source, META_MOBJ);
+ lua_pushinteger(gL, damage);
+ lua_pushinteger(gL, damagetype);
+ }
+ lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+ lua_gettable(gL, LUA_REGISTRYINDEX);
+ lua_pushvalue(gL, -6);
+ lua_pushvalue(gL, -6);
+ lua_pushvalue(gL, -6);
+ lua_pushvalue(gL, -6);
+ lua_pushvalue(gL, -6);
+ if (lua_pcall(gL, 5, 1, 0)) {
+ if (!hookp->error || cv_debug & DBG_LUA)
+ CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+ lua_pop(gL, 1);
+ hookp->error = true;
+ continue;
+ }
+ if (!lua_isnil(gL, -1))
+ {
+ if (lua_toboolean(gL, -1))
+ shouldDamage = 1; // Force yes
+ else
+ shouldDamage = 2; // Force no
+ }
+ lua_pop(gL, 1);
+ }
+
lua_settop(gL, 0);
return shouldDamage;
}
// Hook for P_DamageMobj by mobj type (Mobj actually takes damage!)
-boolean LUAh_MobjDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage)
+boolean LUAh_MobjDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage, UINT8 damagetype)
{
hook_p hookp;
boolean hooked = false;
@@ -469,9 +688,9 @@ boolean LUAh_MobjDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32
lua_settop(gL, 0);
- for (hookp = roothook; hookp; hookp = hookp->next)
- if (hookp->type == hook_MobjDamage
- && (hookp->s.mt == MT_NULL || hookp->s.mt == target->type))
+ // Look for all generic mobj damage hooks
+ for (hookp = mobjhooks[MT_NULL]; hookp; hookp = hookp->next)
+ if (hookp->type == hook_MobjDamage)
{
if (lua_gettop(gL) == 0)
{
@@ -498,12 +717,42 @@ boolean LUAh_MobjDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32
lua_pop(gL, 1);
}
+ for (hookp = mobjhooks[target->type]; hookp; hookp = hookp->next)
+ if (hookp->type == hook_MobjDamage)
+ {
+ if (lua_gettop(gL) == 0)
+ {
+ LUA_PushUserdata(gL, target, META_MOBJ);
+ LUA_PushUserdata(gL, inflictor, META_MOBJ);
+ LUA_PushUserdata(gL, source, META_MOBJ);
+ lua_pushinteger(gL, damage);
+ lua_pushinteger(gL, damagetype);
+ }
+ lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+ lua_gettable(gL, LUA_REGISTRYINDEX);
+ lua_pushvalue(gL, -6);
+ lua_pushvalue(gL, -6);
+ lua_pushvalue(gL, -6);
+ lua_pushvalue(gL, -6);
+ lua_pushvalue(gL, -6);
+ if (lua_pcall(gL, 5, 1, 0)) {
+ if (!hookp->error || cv_debug & DBG_LUA)
+ CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+ lua_pop(gL, 1);
+ hookp->error = true;
+ continue;
+ }
+ if (lua_toboolean(gL, -1))
+ hooked = true;
+ lua_pop(gL, 1);
+ }
+
lua_settop(gL, 0);
return hooked;
}
// Hook for P_KillMobj by mobj type
-boolean LUAh_MobjDeath(mobj_t *target, mobj_t *inflictor, mobj_t *source)
+boolean LUAh_MobjDeath(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damagetype)
{
hook_p hookp;
boolean hooked = false;
@@ -512,9 +761,9 @@ boolean LUAh_MobjDeath(mobj_t *target, mobj_t *inflictor, mobj_t *source)
lua_settop(gL, 0);
- for (hookp = roothook; hookp; hookp = hookp->next)
- if (hookp->type == hook_MobjDeath
- && (hookp->s.mt == MT_NULL || hookp->s.mt == target->type))
+ // Look for all generic mobj death hooks
+ for (hookp = mobjhooks[MT_NULL]; hookp; hookp = hookp->next)
+ if (hookp->type == hook_MobjDeath)
{
if (lua_gettop(gL) == 0)
{
@@ -539,6 +788,34 @@ boolean LUAh_MobjDeath(mobj_t *target, mobj_t *inflictor, mobj_t *source)
lua_pop(gL, 1);
}
+ for (hookp = mobjhooks[target->type]; hookp; hookp = hookp->next)
+ if (hookp->type == hook_MobjDeath)
+ {
+ if (lua_gettop(gL) == 0)
+ {
+ LUA_PushUserdata(gL, target, META_MOBJ);
+ LUA_PushUserdata(gL, inflictor, META_MOBJ);
+ LUA_PushUserdata(gL, source, META_MOBJ);
+ lua_pushinteger(gL, damagetype);
+ }
+ lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+ lua_gettable(gL, LUA_REGISTRYINDEX);
+ lua_pushvalue(gL, -5);
+ lua_pushvalue(gL, -5);
+ lua_pushvalue(gL, -5);
+ lua_pushvalue(gL, -5);
+ if (lua_pcall(gL, 4, 1, 0)) {
+ if (!hookp->error || cv_debug & DBG_LUA)
+ CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+ lua_pop(gL, 1);
+ hookp->error = true;
+ continue;
+ }
+ if (lua_toboolean(gL, -1))
+ hooked = true;
+ lua_pop(gL, 1);
+ }
+
lua_settop(gL, 0);
return hooked;
}
@@ -652,9 +929,8 @@ boolean LUAh_LinedefExecute(line_t *line, mobj_t *mo, sector_t *sector)
lua_settop(gL, 0);
- for (hookp = roothook; hookp; hookp = hookp->next)
- if (hookp->type == hook_LinedefExecute
- && !strcmp(hookp->s.funcname, line->text))
+ for (hookp = linedefexecutorhooks; hookp; hookp = hookp->next)
+ if (!strcmp(hookp->s.funcname, line->text))
{
if (lua_gettop(gL) == 0)
{
@@ -729,7 +1005,7 @@ boolean LUAh_PlayerMsg(int source, int target, int flags, char *msg)
}
// Hook for hurt messages
-boolean LUAh_HurtMsg(player_t *player, mobj_t *inflictor, mobj_t *source)
+boolean LUAh_HurtMsg(player_t *player, mobj_t *inflictor, mobj_t *source, UINT8 damagetype)
{
hook_p hookp;
boolean hooked = false;
@@ -747,13 +1023,15 @@ boolean LUAh_HurtMsg(player_t *player, mobj_t *inflictor, mobj_t *source)
LUA_PushUserdata(gL, player, META_PLAYER);
LUA_PushUserdata(gL, inflictor, META_MOBJ);
LUA_PushUserdata(gL, source, META_MOBJ);
+ lua_pushinteger(gL, damagetype);
}
lua_pushfstring(gL, FMT_HOOKID, hookp->id);
lua_gettable(gL, LUA_REGISTRYINDEX);
- lua_pushvalue(gL, -4);
- lua_pushvalue(gL, -4);
- lua_pushvalue(gL, -4);
- if (lua_pcall(gL, 3, 1, 0)) {
+ lua_pushvalue(gL, -5);
+ lua_pushvalue(gL, -5);
+ lua_pushvalue(gL, -5);
+ lua_pushvalue(gL, -5);
+ if (lua_pcall(gL, 4, 1, 0)) {
if (!hookp->error || cv_debug & DBG_LUA)
CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
lua_pop(gL, 1);
@@ -798,4 +1076,66 @@ void LUAh_NetArchiveHook(lua_CFunction archFunc)
// stack: tables
}
+boolean LUAh_MapThingSpawn(mobj_t *mo, mapthing_t *mthing)
+{
+ hook_p hookp;
+ boolean hooked = false;
+ if (!gL || !(hooksAvailable[hook_MapThingSpawn/8] & (1<<(hook_MapThingSpawn%8))))
+ return false;
+
+ lua_settop(gL, 0);
+
+ // Look for all generic mobj map thing spawn hooks
+ for (hookp = mobjhooks[MT_NULL]; hookp; hookp = hookp->next)
+ if (hookp->type == hook_MapThingSpawn)
+ {
+ if (lua_gettop(gL) == 0)
+ {
+ LUA_PushUserdata(gL, mo, META_MOBJ);
+ LUA_PushUserdata(gL, mthing, META_MAPTHING);
+ }
+ lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+ lua_gettable(gL, LUA_REGISTRYINDEX);
+ lua_pushvalue(gL, -3);
+ lua_pushvalue(gL, -3);
+ if (lua_pcall(gL, 2, 1, 0)) {
+ if (!hookp->error || cv_debug & DBG_LUA)
+ CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+ lua_pop(gL, 1);
+ hookp->error = true;
+ continue;
+ }
+ if (lua_toboolean(gL, -1))
+ hooked = true;
+ lua_pop(gL, 1);
+ }
+
+ for (hookp = mobjhooks[mo->type]; hookp; hookp = hookp->next)
+ if (hookp->type == hook_MapThingSpawn)
+ {
+ if (lua_gettop(gL) == 0)
+ {
+ LUA_PushUserdata(gL, mo, META_MOBJ);
+ LUA_PushUserdata(gL, mthing, META_MAPTHING);
+ }
+ lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+ lua_gettable(gL, LUA_REGISTRYINDEX);
+ lua_pushvalue(gL, -3);
+ lua_pushvalue(gL, -3);
+ if (lua_pcall(gL, 2, 1, 0)) {
+ if (!hookp->error || cv_debug & DBG_LUA)
+ CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+ lua_pop(gL, 1);
+ hookp->error = true;
+ continue;
+ }
+ if (lua_toboolean(gL, -1))
+ hooked = true;
+ lua_pop(gL, 1);
+ }
+
+ lua_settop(gL, 0);
+ return hooked;
+}
+
#endif
diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index 7aadd9c0e..5b3cd46ce 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -226,7 +226,12 @@ static int hudinfo_num(lua_State *L)
static int colormap_get(lua_State *L)
{
- return luaL_error(L, "colormap is not a struct.");
+ const UINT8 *colormap = *((UINT8 **)luaL_checkudata(L, 1, META_COLORMAP));
+ UINT32 i = luaL_checkinteger(L, 2);
+ if (i >= 256)
+ return luaL_error(L, "colormap index %d out of range (0 - %d)", i, 255);
+ lua_pushinteger(L, colormap[i]);
+ return 1;
}
static int patch_get(lua_State *L)
@@ -369,6 +374,8 @@ static int libd_drawScaled(lua_State *L)
x = luaL_checkinteger(L, 1);
y = luaL_checkinteger(L, 2);
scale = luaL_checkinteger(L, 3);
+ if (scale < 0)
+ return luaL_error(L, "negative scale");
patch = *((patch_t **)luaL_checkudata(L, 4, META_PATCH));
flags = luaL_optinteger(L, 5, 0);
if (!lua_isnoneornil(L, 6))
diff --git a/src/lua_infolib.c b/src/lua_infolib.c
index 4f7fdaa26..9361abe94 100644
--- a/src/lua_infolib.c
+++ b/src/lua_infolib.c
@@ -31,6 +31,7 @@ enum sfxinfo_read {
sfxinfor_singular,
sfxinfor_priority,
sfxinfor_flags, // "pitch"
+ sfxinfor_caption,
sfxinfor_skinsound
};
const char *const sfxinfo_ropt[] = {
@@ -38,18 +39,21 @@ const char *const sfxinfo_ropt[] = {
"singular",
"priority",
"flags",
+ "caption",
"skinsound",
NULL};
enum sfxinfo_write {
sfxinfow_singular = 0,
sfxinfow_priority,
- sfxinfow_flags // "pitch"
+ sfxinfow_flags, // "pitch"
+ sfxinfow_caption
};
const char *const sfxinfo_wopt[] = {
"singular",
"priority",
"flags",
+ "caption",
NULL};
//
@@ -769,8 +773,8 @@ static int lib_getSfxInfo(lua_State *L)
lua_remove(L, 1);
i = luaL_checkinteger(L, 1);
- if (i >= NUMSFX)
- return luaL_error(L, "sfxinfo[] index %d out of range (0 - %d)", i, NUMSFX-1);
+ if (i == 0 || i >= NUMSFX)
+ return luaL_error(L, "sfxinfo[] index %d out of range (1 - %d)", i, NUMSFX-1);
LUA_PushUserdata(L, &S_sfx[i], META_SFXINFO);
return 1;
}
@@ -783,9 +787,9 @@ static int lib_setSfxInfo(lua_State *L)
lua_remove(L, 1);
{
UINT32 i = luaL_checkinteger(L, 1);
- if (i >= NUMSFX)
- return luaL_error(L, "sfxinfo[] index %d out of range (0 - %d)", i, NUMSFX-1);
- info = &S_sfx[i]; // get the mobjinfo to assign to.
+ if (i == 0 || i >= NUMSFX)
+ return luaL_error(L, "sfxinfo[] index %d out of range (1 - %d)", i, NUMSFX-1);
+ info = &S_sfx[i]; // get the sfxinfo to assign to.
}
luaL_checktype(L, 2, LUA_TTABLE); // check that we've been passed a table.
lua_remove(L, 1); // pop mobjtype num, don't need it any more.
@@ -814,6 +818,9 @@ static int lib_setSfxInfo(lua_State *L)
case sfxinfow_flags:
info->pitch = (INT32)luaL_checkinteger(L, 3);
break;
+ case sfxinfow_caption:
+ strlcpy(info->caption, luaL_checkstring(L, 3), sizeof(info->caption));
+ break;
default:
break;
}
@@ -851,11 +858,14 @@ static int sfxinfo_get(lua_State *L)
case sfxinfor_flags:
lua_pushinteger(L, sfx->pitch);
return 1;
+ case sfxinfor_caption:
+ lua_pushstring(L, sfx->caption);
+ return 1;
case sfxinfor_skinsound:
lua_pushinteger(L, sfx->skinsound);
return 1;
default:
- return luaL_error(L, "impossible error");
+ return luaL_error(L, "Field does not exist in sfxinfo_t");
}
return 0;
}
@@ -886,8 +896,11 @@ static int sfxinfo_set(lua_State *L)
case sfxinfow_flags:
sfx->pitch = luaL_checkinteger(L, 1);
break;
+ case sfxinfow_caption:
+ strlcpy(sfx->caption, luaL_checkstring(L, 1), sizeof(sfx->caption));
+ break;
default:
- return luaL_error(L, "impossible error");
+ return luaL_error(L, "Field does not exist in sfxinfo_t");
}
return 0;
}
diff --git a/src/lua_libs.h b/src/lua_libs.h
index 931cf62d0..fd4937b63 100644
--- a/src/lua_libs.h
+++ b/src/lua_libs.h
@@ -38,12 +38,22 @@ extern lua_State *gL;
#define META_SUBSECTOR "SUBSECTOR_T*"
#define META_SECTOR "SECTOR_T*"
#define META_FFLOOR "FFLOOR_T*"
+#ifdef HAVE_LUA_SEGS
+#define META_SEG "SEG_T*"
+#define META_NODE "NODE_T*"
+#endif
#define META_MAPHEADER "MAPHEADER_T*"
#define META_CVAR "CONSVAR_T*"
#define META_SECTORLINES "SECTOR_T*LINES"
#define META_SIDENUM "LINE_T*SIDENUM"
+#ifdef HAVE_LUA_SEGS
+#define META_NODEBBOX "NODE_T*BBOX"
+#define META_NODECHILDREN "NODE_T*CHILDREN"
+#endif
+
+#define META_BBOX "BOUNDING_BOX"
#define META_HUDINFO "HUDINFO_T*"
#define META_PATCH "PATCH_T*"
@@ -64,6 +74,7 @@ int LUA_PlayerLib(lua_State *L);
int LUA_SkinLib(lua_State *L);
int LUA_ThinkerLib(lua_State *L);
int LUA_MapLib(lua_State *L);
+int LUA_BlockmapLib(lua_State *L);
int LUA_HudLib(lua_State *L);
#endif
diff --git a/src/lua_maplib.c b/src/lua_maplib.c
index c512bf3c5..f1bfcb8f1 100644
--- a/src/lua_maplib.c
+++ b/src/lua_maplib.c
@@ -185,15 +185,98 @@ static const char *const ffloor_opt[] = {
"alpha",
NULL};
+#ifdef HAVE_LUA_SEGS
+enum seg_e {
+ seg_valid = 0,
+ seg_v1,
+ seg_v2,
+ seg_side,
+ seg_offset,
+ seg_angle,
+ seg_sidedef,
+ seg_linedef,
+ seg_frontsector,
+ seg_backsector,
+};
+
+static const char *const seg_opt[] = {
+ "valid",
+ "v1",
+ "v2",
+ "side",
+ "offset",
+ "angle",
+ "sidedef",
+ "linedef",
+ "frontsector",
+ "backsector",
+ NULL};
+
+enum node_e {
+ node_valid = 0,
+ node_x,
+ node_y,
+ node_dx,
+ node_dy,
+ node_bbox,
+ node_children,
+};
+
+static const char *const node_opt[] = {
+ "valid",
+ "x",
+ "y",
+ "dx",
+ "dy",
+ "bbox",
+ "children",
+ NULL};
+
+enum nodechild_e {
+ nodechild_valid = 0,
+ nodechild_right,
+ nodechild_left,
+};
+
+static const char *const nodechild_opt[] = {
+ "valid",
+ "right",
+ "left",
+ NULL};
+#endif
+
+enum bbox_e {
+ bbox_valid = 0,
+ bbox_top,
+ bbox_bottom,
+ bbox_left,
+ bbox_right,
+};
+
+static const char *const bbox_opt[] = {
+ "valid",
+ "top",
+ "bottom",
+ "left",
+ "right",
+ NULL};
+
static const char *const array_opt[] ={"iterate",NULL};
static const char *const valid_opt[] ={"valid",NULL};
+///////////////////////////////////
+// sector list iterate functions //
+///////////////////////////////////
+
// iterates through a sector's thinglist!
static int lib_iterateSectorThinglist(lua_State *L)
{
mobj_t *state = NULL;
mobj_t *thing = NULL;
+ if (gamestate != GS_LEVEL)
+ return luaL_error(L, "This function can only be used in a level!");
+
if (lua_gettop(L) < 2)
return luaL_error(L, "Don't call sector.thinglist() directly, use it as 'for rover in sector.thinglist do end'.");
@@ -227,6 +310,9 @@ static int lib_iterateSectorFFloors(lua_State *L)
ffloor_t *state = NULL;
ffloor_t *ffloor = NULL;
+ if (gamestate != GS_LEVEL)
+ return luaL_error(L, "This function can only be used in a level!");
+
if (lua_gettop(L) < 2)
return luaL_error(L, "Don't call sector.ffloors() directly, use it as 'for rover in sector.ffloors do end'.");
@@ -262,6 +348,10 @@ static int sector_iterate(lua_State *L)
return 3;
}
+////////////////////
+// sector.lines[] //
+////////////////////
+
// sector.lines, i -> sector.lines[i]
// sector.lines.valid, for validity checking
static int sectorlines_get(lua_State *L)
@@ -323,6 +413,10 @@ static int sectorlines_num(lua_State *L)
return 1;
}
+//////////////
+// sector_t //
+//////////////
+
static int sector_get(lua_State *L)
{
sector_t *sector = *((sector_t **)luaL_checkudata(L, 1, META_SECTOR));
@@ -348,22 +442,12 @@ static int sector_get(lua_State *L)
case sector_ceilingheight:
lua_pushfixed(L, sector->ceilingheight);
return 1;
- case sector_floorpic: { // floorpic
- levelflat_t *levelflat;
- INT16 i;
- for (i = 0, levelflat = levelflats; i != sector->floorpic; i++, levelflat++)
- ;
- lua_pushlstring(L, levelflat->name, 8);
+ case sector_floorpic: // floorpic
+ lua_pushlstring(L, levelflats[sector->floorpic].name, 8);
return 1;
- }
- case sector_ceilingpic: { // ceilingpic
- levelflat_t *levelflat;
- INT16 i;
- for (i = 0, levelflat = levelflats; i != sector->ceilingpic; i++, levelflat++)
- ;
- lua_pushlstring(L, levelflat->name, 8);
+ case sector_ceilingpic: // ceilingpic
+ lua_pushlstring(L, levelflats[sector->ceilingpic].name, 8);
return 1;
- }
case sector_lightlevel:
lua_pushinteger(L, sector->lightlevel);
return 1;
@@ -400,46 +484,6 @@ static int sector_get(lua_State *L)
return 0;
}
-// help function for P_LoadSectors, find a flat in the active wad files,
-// allocate an id for it, and set the levelflat (to speedup search)
-//
-static INT32 P_AddLevelFlatRuntime(const char *flatname)
-{
- size_t i;
- levelflat_t *levelflat = levelflats;
-
- //
- // first scan through the already found flats
- //
- for (i = 0; i < numlevelflats; i++, levelflat++)
- if (strnicmp(levelflat->name,flatname,8)==0)
- break;
-
- // that flat was already found in the level, return the id
- if (i == numlevelflats)
- {
- // allocate new flat memory
- levelflats = Z_Realloc(levelflats, (numlevelflats + 1) * sizeof(*levelflats), PU_LEVEL, NULL);
- levelflat = levelflats+i;
-
- // store the name
- strlcpy(levelflat->name, flatname, sizeof (levelflat->name));
- strupr(levelflat->name);
-
- // store the flat lump number
- levelflat->lumpnum = R_GetFlatNumForName(flatname);
-
-#ifndef ZDEBUG
- CONS_Debug(DBG_SETUP, "flat #%03d: %s\n", atoi(sizeu1(numlevelflats)), levelflat->name);
-#endif
-
- numlevelflats++;
- }
-
- // level flat id
- return (INT32)i;
-}
-
static int sector_set(lua_State *L)
{
sector_t *sector = *((sector_t **)luaL_checkudata(L, 1, META_SECTOR));
@@ -514,6 +558,10 @@ static int sector_num(lua_State *L)
return 1;
}
+/////////////////
+// subsector_t //
+/////////////////
+
static int subsector_get(lua_State *L)
{
subsector_t *subsector = *((subsector_t **)luaL_checkudata(L, 1, META_SUBSECTOR));
@@ -553,6 +601,10 @@ static int subsector_num(lua_State *L)
return 1;
}
+////////////
+// line_t //
+////////////
+
static int line_get(lua_State *L)
{
line_t *line = *((line_t **)luaL_checkudata(L, 1, META_LINE));
@@ -650,6 +702,10 @@ static int line_num(lua_State *L)
return 1;
}
+////////////////////
+// line.sidenum[] //
+////////////////////
+
static int sidenum_get(lua_State *L)
{
UINT16 *sidenum = *((UINT16 **)luaL_checkudata(L, 1, META_SIDENUM));
@@ -678,6 +734,10 @@ static int sidenum_get(lua_State *L)
return 1;
}
+////////////
+// side_t //
+////////////
+
static int side_get(lua_State *L)
{
side_t *side = *((side_t **)luaL_checkudata(L, 1, META_SIDE));
@@ -779,6 +839,10 @@ static int side_num(lua_State *L)
return 1;
}
+//////////////
+// vertex_t //
+//////////////
+
static int vertex_get(lua_State *L)
{
vertex_t *vertex = *((vertex_t **)luaL_checkudata(L, 1, META_VERTEX));
@@ -818,9 +882,293 @@ static int vertex_num(lua_State *L)
return 1;
}
+#ifdef HAVE_LUA_SEGS
+
+///////////
+// seg_t //
+///////////
+
+static int seg_get(lua_State *L)
+{
+ seg_t *seg = *((seg_t **)luaL_checkudata(L, 1, META_SEG));
+ enum seg_e field = luaL_checkoption(L, 2, seg_opt[0], seg_opt);
+
+ if (!seg)
+ {
+ if (field == seg_valid) {
+ lua_pushboolean(L, 0);
+ return 1;
+ }
+ return luaL_error(L, "accessed seg_t doesn't exist anymore.");
+ }
+
+ switch(field)
+ {
+ case seg_valid: // valid
+ lua_pushboolean(L, 1);
+ return 1;
+ case seg_v1:
+ LUA_PushUserdata(L, seg->v1, META_VERTEX);
+ return 1;
+ case seg_v2:
+ LUA_PushUserdata(L, seg->v2, META_VERTEX);
+ return 1;
+ case seg_side:
+ lua_pushinteger(L, seg->side);
+ return 1;
+ case seg_offset:
+ lua_pushfixed(L, seg->offset);
+ return 1;
+ case seg_angle:
+ lua_pushangle(L, seg->angle);
+ return 1;
+ case seg_sidedef:
+ LUA_PushUserdata(L, seg->sidedef, META_SIDE);
+ return 1;
+ case seg_linedef:
+ LUA_PushUserdata(L, seg->linedef, META_LINE);
+ return 1;
+ case seg_frontsector:
+ LUA_PushUserdata(L, seg->frontsector, META_SECTOR);
+ return 1;
+ case seg_backsector:
+ LUA_PushUserdata(L, seg->backsector, META_SECTOR);
+ return 1;
+ }
+ return 0;
+}
+
+static int seg_num(lua_State *L)
+{
+ seg_t *seg = *((seg_t **)luaL_checkudata(L, 1, META_SEG));
+ lua_pushinteger(L, seg-segs);
+ return 1;
+}
+
+////////////
+// node_t //
+////////////
+
+static int node_get(lua_State *L)
+{
+ node_t *node = *((node_t **)luaL_checkudata(L, 1, META_NODE));
+ enum node_e field = luaL_checkoption(L, 2, node_opt[0], node_opt);
+
+ if (!node)
+ {
+ if (field == node_valid) {
+ lua_pushboolean(L, 0);
+ return 1;
+ }
+ return luaL_error(L, "accessed node_t doesn't exist anymore.");
+ }
+
+ switch(field)
+ {
+ case node_valid: // valid
+ lua_pushboolean(L, 1);
+ return 1;
+ case node_x:
+ lua_pushfixed(L, node->x);
+ return 1;
+ case node_y:
+ lua_pushfixed(L, node->y);
+ return 1;
+ case node_dx:
+ lua_pushfixed(L, node->x);
+ return 1;
+ case node_dy:
+ lua_pushfixed(L, node->x);
+ return 1;
+ case node_bbox:
+ LUA_PushUserdata(L, node->bbox, META_NODEBBOX);
+ return 1;
+ case node_children:
+ LUA_PushUserdata(L, node->children, META_NODECHILDREN);
+ return 1;
+ }
+ return 0;
+}
+
+static int node_num(lua_State *L)
+{
+ node_t *node = *((node_t **)luaL_checkudata(L, 1, META_NODE));
+ lua_pushinteger(L, node-nodes);
+ return 1;
+}
+
+///////////////
+// node.bbox //
+///////////////
+
+/*
+// node.bbox[i][j]: i = 0 or 1, j = 0 1 2 or 3
+// NOTE: 2D arrays are NOT double pointers,
+// the second bbox will be directly after the first in memory (hence the way the bbox is pushed here)
+// this function handles the [i] part, bbox_get handles the [j] part
+static int nodebbox_get(lua_State *L)
+{
+ fixed_t *bbox = *((fixed_t **)luaL_checkudata(L, 1, META_NODEBBOX));
+ int i;
+ lua_settop(L, 2);
+ if (!lua_isnumber(L, 2))
+ {
+ int field = luaL_checkoption(L, 2, NULL, valid_opt);
+ if (!bbox)
+ {
+ if (field == 0) {
+ lua_pushboolean(L, 0);
+ return 1;
+ }
+ return luaL_error(L, "accessed node_t doesn't exist anymore.");
+ } else if (field == 0) {
+ lua_pushboolean(L, 1);
+ return 1;
+ }
+ }
+
+ i = lua_tointeger(L, 2);
+ if (i < 0 || i > 1)
+ return 0;
+ LUA_PushUserdata(L, bbox + i*4*sizeof(fixed_t), META_BBOX);
+ return 1;
+}
+*/
+static int nodebbox_call(lua_State *L)
+{
+ fixed_t *bbox = *((fixed_t **)luaL_checkudata(L, 1, META_NODEBBOX));
+ int i, j;
+ int n = lua_gettop(L);
+
+ if (!bbox)
+ return luaL_error(L, "accessed node bbox doesn't exist anymore.");
+ if (n < 3)
+ return luaL_error(L, "arguments 2 and/or 3 not given (expected node.bbox(child, coord))");
+ // get child
+ if (!lua_isnumber(L, 2)) {
+ enum nodechild_e field = luaL_checkoption(L, 2, nodechild_opt[0], nodechild_opt);
+ switch (field) {
+ case nodechild_right: i = 0; break;
+ case nodechild_left: i = 1; break;
+ default:
+ return luaL_error(L, "invalid node child \"%s\".", lua_tostring(L, 2));
+ }
+ }
+ else {
+ i = lua_tointeger(L, 2);
+ if (i < 0 || i > 1)
+ return 0;
+ }
+ // get bbox coord
+ if (!lua_isnumber(L, 3)) {
+ enum bbox_e field = luaL_checkoption(L, 3, bbox_opt[0], bbox_opt);
+ switch (field) {
+ case bbox_top: j = BOXTOP; break;
+ case bbox_bottom: j = BOXBOTTOM; break;
+ case bbox_left: j = BOXLEFT; break;
+ case bbox_right: j = BOXRIGHT; break;
+ default:
+ return luaL_error(L, "invalid bbox coordinate \"%s\".", lua_tostring(L, 3));
+ }
+ }
+ else {
+ j = lua_tointeger(L, 3);
+ if (j < 0 || j > 3)
+ return 0;
+ }
+ lua_pushinteger(L, bbox[i*4 + j]);
+ return 1;
+}
+
+/////////////////////
+// node.children[] //
+/////////////////////
+
+// node.children[i]: i = 0 or 1
+static int nodechildren_get(lua_State *L)
+{
+ UINT16 *children = *((UINT16 **)luaL_checkudata(L, 1, META_NODECHILDREN));
+ int i;
+ lua_settop(L, 2);
+ if (!lua_isnumber(L, 2))
+ {
+ enum nodechild_e field = luaL_checkoption(L, 2, nodechild_opt[0], nodechild_opt);
+ if (!children)
+ {
+ if (field == nodechild_valid) {
+ lua_pushboolean(L, 0);
+ return 1;
+ }
+ return luaL_error(L, "accessed node_t doesn't exist anymore.");
+ } else if (field == nodechild_valid) {
+ lua_pushboolean(L, 1);
+ return 1;
+ } else switch (field) {
+ case nodechild_right: i = 0; break;
+ case nodechild_left: i = 1; break;
+ default: return 0;
+ }
+ }
+ else {
+ i = lua_tointeger(L, 2);
+ if (i < 0 || i > 1)
+ return 0;
+ }
+ lua_pushinteger(L, children[i]);
+ return 1;
+}
+#endif
+
+//////////
+// bbox //
+//////////
+
+// bounding box (aka fixed_t array with four elements)
+// NOTE: may be useful for polyobjects or other things later
+static int bbox_get(lua_State *L)
+{
+ fixed_t *bbox = *((fixed_t **)luaL_checkudata(L, 1, META_BBOX));
+ int i;
+ lua_settop(L, 2);
+ if (!lua_isnumber(L, 2))
+ {
+ enum bbox_e field = luaL_checkoption(L, 2, bbox_opt[0], bbox_opt);
+ if (!bbox)
+ {
+ if (field == bbox_valid) {
+ lua_pushboolean(L, 0);
+ return 1;
+ }
+ return luaL_error(L, "accessed bbox doesn't exist anymore.");
+ } else if (field == bbox_valid) {
+ lua_pushboolean(L, 1);
+ return 1;
+ } else switch (field) {
+ case bbox_top: i = BOXTOP; break;
+ case bbox_bottom: i = BOXBOTTOM; break;
+ case bbox_left: i = BOXLEFT; break;
+ case bbox_right: i = BOXRIGHT; break;
+ default: return 0;
+ }
+ }
+ else {
+ i = lua_tointeger(L, 2);
+ if (i < 0 || i > 3)
+ return 0;
+ }
+ lua_pushinteger(L, bbox[i]);
+ return 1;
+}
+
+///////////////
+// sectors[] //
+///////////////
+
static int lib_iterateSectors(lua_State *L)
{
size_t i = 0;
+ if (gamestate != GS_LEVEL)
+ return luaL_error(L, "This function can only be used in a level!");
if (lua_gettop(L) < 2)
return luaL_error(L, "Don't call sectors.iterate() directly, use it as 'for sector in sectors.iterate do end'.");
lua_settop(L, 2);
@@ -838,6 +1186,8 @@ static int lib_iterateSectors(lua_State *L)
static int lib_getSector(lua_State *L)
{
int field;
+ if (gamestate != GS_LEVEL)
+ return luaL_error(L, "You cannot access this outside of a level!");
lua_settop(L, 2);
lua_remove(L, 1); // dummy userdata table is unused.
if (lua_isnumber(L, 1))
@@ -864,9 +1214,15 @@ static int lib_numsectors(lua_State *L)
return 1;
}
+//////////////////
+// subsectors[] //
+//////////////////
+
static int lib_iterateSubsectors(lua_State *L)
{
size_t i = 0;
+ if (gamestate != GS_LEVEL)
+ return luaL_error(L, "This function can only be used in a level!");
if (lua_gettop(L) < 2)
return luaL_error(L, "Don't call subsectors.iterate() directly, use it as 'for subsector in subsectors.iterate do end'.");
lua_settop(L, 2);
@@ -884,6 +1240,8 @@ static int lib_iterateSubsectors(lua_State *L)
static int lib_getSubsector(lua_State *L)
{
int field;
+ if (gamestate != GS_LEVEL)
+ return luaL_error(L, "You cannot access this outside of a level!");
lua_settop(L, 2);
lua_remove(L, 1); // dummy userdata table is unused.
if (lua_isnumber(L, 1))
@@ -910,9 +1268,15 @@ static int lib_numsubsectors(lua_State *L)
return 1;
}
+/////////////
+// lines[] //
+/////////////
+
static int lib_iterateLines(lua_State *L)
{
size_t i = 0;
+ if (gamestate != GS_LEVEL)
+ return luaL_error(L, "This function can only be used in a level!");
if (lua_gettop(L) < 2)
return luaL_error(L, "Don't call lines.iterate() directly, use it as 'for line in lines.iterate do end'.");
lua_settop(L, 2);
@@ -930,6 +1294,8 @@ static int lib_iterateLines(lua_State *L)
static int lib_getLine(lua_State *L)
{
int field;
+ if (gamestate != GS_LEVEL)
+ return luaL_error(L, "You cannot access this outside of a level!");
lua_settop(L, 2);
lua_remove(L, 1); // dummy userdata table is unused.
if (lua_isnumber(L, 1))
@@ -956,9 +1322,15 @@ static int lib_numlines(lua_State *L)
return 1;
}
+/////////////
+// sides[] //
+/////////////
+
static int lib_iterateSides(lua_State *L)
{
size_t i = 0;
+ if (gamestate != GS_LEVEL)
+ return luaL_error(L, "This function can only be used in a level!");
if (lua_gettop(L) < 2)
return luaL_error(L, "Don't call sides.iterate() directly, use it as 'for side in sides.iterate do end'.");
lua_settop(L, 2);
@@ -976,6 +1348,8 @@ static int lib_iterateSides(lua_State *L)
static int lib_getSide(lua_State *L)
{
int field;
+ if (gamestate != GS_LEVEL)
+ return luaL_error(L, "You cannot access this outside of a level!");
lua_settop(L, 2);
lua_remove(L, 1); // dummy userdata table is unused.
if (lua_isnumber(L, 1))
@@ -1002,9 +1376,15 @@ static int lib_numsides(lua_State *L)
return 1;
}
+////////////////
+// vertexes[] //
+////////////////
+
static int lib_iterateVertexes(lua_State *L)
{
size_t i = 0;
+ if (gamestate != GS_LEVEL)
+ return luaL_error(L, "This function can only be used in a level!");
if (lua_gettop(L) < 2)
return luaL_error(L, "Don't call vertexes.iterate() directly, use it as 'for vertex in vertexes.iterate do end'.");
lua_settop(L, 2);
@@ -1022,6 +1402,8 @@ static int lib_iterateVertexes(lua_State *L)
static int lib_getVertex(lua_State *L)
{
int field;
+ if (gamestate != GS_LEVEL)
+ return luaL_error(L, "You cannot access this outside of a level!");
lua_settop(L, 2);
lua_remove(L, 1); // dummy userdata table is unused.
if (lua_isnumber(L, 1))
@@ -1048,6 +1430,121 @@ static int lib_numvertexes(lua_State *L)
return 1;
}
+#ifdef HAVE_LUA_SEGS
+
+////////////
+// segs[] //
+////////////
+
+static int lib_iterateSegs(lua_State *L)
+{
+ size_t i = 0;
+ if (gamestate != GS_LEVEL)
+ return luaL_error(L, "This function can only be used in a level!");
+ if (lua_gettop(L) < 2)
+ return luaL_error(L, "Don't call segs.iterate() directly, use it as 'for seg in segs.iterate do end'.");
+ lua_settop(L, 2);
+ lua_remove(L, 1); // state is unused.
+ if (!lua_isnil(L, 1))
+ i = (size_t)(*((seg_t **)luaL_checkudata(L, 1, META_SEG)) - segs)+1;
+ if (i < numsegs)
+ {
+ LUA_PushUserdata(L, &segs[i], META_SEG);
+ return 1;
+ }
+ return 0;
+}
+
+static int lib_getSeg(lua_State *L)
+{
+ int field;
+ if (gamestate != GS_LEVEL)
+ return luaL_error(L, "You cannot access this outside of a level!");
+ lua_settop(L, 2);
+ lua_remove(L, 1); // dummy userdata table is unused.
+ if (lua_isnumber(L, 1))
+ {
+ size_t i = lua_tointeger(L, 1);
+ if (i >= numsegs)
+ return 0;
+ LUA_PushUserdata(L, &segs[i], META_SEG);
+ return 1;
+ }
+ field = luaL_checkoption(L, 1, NULL, array_opt);
+ switch(field)
+ {
+ case 0: // iterate
+ lua_pushcfunction(L, lib_iterateSegs);
+ return 1;
+ }
+ return 0;
+}
+
+static int lib_numsegs(lua_State *L)
+{
+ lua_pushinteger(L, numsegs);
+ return 1;
+}
+
+/////////////
+// nodes[] //
+/////////////
+
+static int lib_iterateNodes(lua_State *L)
+{
+ size_t i = 0;
+ if (gamestate != GS_LEVEL)
+ return luaL_error(L, "This function can only be used in a level!");
+ if (lua_gettop(L) < 2)
+ return luaL_error(L, "Don't call nodes.iterate() directly, use it as 'for node in nodes.iterate do end'.");
+ lua_settop(L, 2);
+ lua_remove(L, 1); // state is unused.
+ if (!lua_isnil(L, 1))
+ i = (size_t)(*((node_t **)luaL_checkudata(L, 1, META_NODE)) - nodes)+1;
+ if (i < numsegs)
+ {
+ LUA_PushUserdata(L, &nodes[i], META_NODE);
+ return 1;
+ }
+ return 0;
+}
+
+static int lib_getNode(lua_State *L)
+{
+ int field;
+ if (gamestate != GS_LEVEL)
+ return luaL_error(L, "You cannot access this outside of a level!");
+ lua_settop(L, 2);
+ lua_remove(L, 1); // dummy userdata table is unused.
+ if (lua_isnumber(L, 1))
+ {
+ size_t i = lua_tointeger(L, 1);
+ if (i >= numnodes)
+ return 0;
+ LUA_PushUserdata(L, &nodes[i], META_NODE);
+ return 1;
+ }
+ field = luaL_checkoption(L, 1, NULL, array_opt);
+ switch(field)
+ {
+ case 0: // iterate
+ lua_pushcfunction(L, lib_iterateNodes);
+ return 1;
+ }
+ return 0;
+}
+
+static int lib_numnodes(lua_State *L)
+{
+ lua_pushinteger(L, numnodes);
+ return 1;
+}
+#endif
+
+//////////////
+// ffloor_t //
+//////////////
+
static int ffloor_get(lua_State *L)
{
ffloor_t *ffloor = *((ffloor_t **)luaL_checkudata(L, 1, META_FFLOOR));
@@ -1191,6 +1688,10 @@ static int ffloor_set(lua_State *L)
return 0;
}
+/////////////////////
+// mapheaderinfo[] //
+/////////////////////
+
static int lib_getMapheaderinfo(lua_State *L)
{
// i -> mapheaderinfo[i-1]
@@ -1223,6 +1724,10 @@ static int lib_nummapheaders(lua_State *L)
return 1;
}
+/////////////////
+// mapheader_t //
+/////////////////
+
static int mapheaderinfo_get(lua_State *L)
{
mapheader_t *header = *((mapheader_t **)luaL_checkudata(L, 1, META_MAPHEADER));
@@ -1367,6 +1872,41 @@ int LUA_MapLib(lua_State *L)
lua_setfield(L, -2, "__newindex");
lua_pop(L, 1);
+#ifdef HAVE_LUA_SEGS
+ luaL_newmetatable(L, META_SEG);
+ lua_pushcfunction(L, seg_get);
+ lua_setfield(L, -2, "__index");
+
+ lua_pushcfunction(L, seg_num);
+ lua_setfield(L, -2, "__len");
+ lua_pop(L, 1);
+
+ luaL_newmetatable(L, META_NODE);
+ lua_pushcfunction(L, node_get);
+ lua_setfield(L, -2, "__index");
+
+ lua_pushcfunction(L, node_num);
+ lua_setfield(L, -2, "__len");
+ lua_pop(L, 1);
+
+ luaL_newmetatable(L, META_NODEBBOX);
+ //lua_pushcfunction(L, nodebbox_get);
+ //lua_setfield(L, -2, "__index");
+ lua_pushcfunction(L, nodebbox_call);
+ lua_setfield(L, -2, "__call");
+ lua_pop(L, 1);
+
+ luaL_newmetatable(L, META_NODECHILDREN);
+ lua_pushcfunction(L, nodechildren_get);
+ lua_setfield(L, -2, "__index");
+ lua_pop(L, 1);
+#endif
+
+ luaL_newmetatable(L, META_BBOX);
+ lua_pushcfunction(L, bbox_get);
+ lua_setfield(L, -2, "__index");
+ lua_pop(L, 1);
+
luaL_newmetatable(L, META_MAPHEADER);
lua_pushcfunction(L, mapheaderinfo_get);
lua_setfield(L, -2, "__index");
@@ -1425,6 +1965,28 @@ int LUA_MapLib(lua_State *L)
lua_setmetatable(L, -2);
lua_setglobal(L, "vertexes");
+#ifdef HAVE_LUA_SEGS
+ lua_newuserdata(L, 0);
+ lua_createtable(L, 0, 2);
+ lua_pushcfunction(L, lib_getSeg);
+ lua_setfield(L, -2, "__index");
+
+ lua_pushcfunction(L, lib_numsegs);
+ lua_setfield(L, -2, "__len");
+ lua_setmetatable(L, -2);
+ lua_setglobal(L, "segs");
+
+ lua_newuserdata(L, 0);
+ lua_createtable(L, 0, 2);
+ lua_pushcfunction(L, lib_getNode);
+ lua_setfield(L, -2, "__index");
+
+ lua_pushcfunction(L, lib_numnodes);
+ lua_setfield(L, -2, "__len");
+ lua_setmetatable(L, -2);
+ lua_setglobal(L, "nodes");
+#endif
+
lua_newuserdata(L, 0);
lua_createtable(L, 0, 2);
lua_pushcfunction(L, lib_getMapheaderinfo);
diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c
index 6bb1388fc..2fcccab66 100644
--- a/src/lua_mobjlib.c
+++ b/src/lua_mobjlib.c
@@ -34,6 +34,7 @@ enum mobj_e {
mobj_angle,
mobj_sprite,
mobj_frame,
+ mobj_sprite2,
mobj_anim_duration,
mobj_touching_sectorlist,
mobj_subsector,
@@ -93,6 +94,7 @@ static const char *const mobj_opt[] = {
"angle",
"sprite",
"frame",
+ "sprite2",
"anim_duration",
"touching_sectorlist",
"subsector",
@@ -189,6 +191,9 @@ static int mobj_get(lua_State *L)
case mobj_frame:
lua_pushinteger(L, mo->frame);
break;
+ case mobj_sprite2:
+ lua_pushinteger(L, mo->sprite2);
+ break;
case mobj_anim_duration:
lua_pushinteger(L, mo->anim_duration);
break;
@@ -411,6 +416,9 @@ static int mobj_set(lua_State *L)
case mobj_frame:
mo->frame = (UINT32)luaL_checkinteger(L, 3);
break;
+ case mobj_sprite2:
+ mo->sprite2 = P_GetMobjSprite2(mo, (UINT8)luaL_checkinteger(L, 3));
+ break;
case mobj_anim_duration:
mo->anim_duration = (UINT16)luaL_checkinteger(L, 3);
break;
@@ -503,7 +511,8 @@ static int mobj_set(lua_State *L)
for (i = 0; i < numskins; i++)
if (fastcmp(skins[i].name, skin))
{
- mo->skin = &skins[i];
+ if (!mo->player || R_SkinUsable(mo->player-players, i))
+ mo->skin = &skins[i];
return 0;
}
return luaL_error(L, "mobj.skin '%s' not found!", skin);
@@ -743,6 +752,8 @@ static int mapthing_set(lua_State *L)
static int lib_iterateMapthings(lua_State *L)
{
size_t i = 0;
+ if (gamestate != GS_LEVEL)
+ return luaL_error(L, "This function can only be used in a level!");
if (lua_gettop(L) < 2)
return luaL_error(L, "Don't call mapthings.iterate() directly, use it as 'for mapthing in mapthings.iterate do end'.");
lua_settop(L, 2);
@@ -760,6 +771,8 @@ static int lib_iterateMapthings(lua_State *L)
static int lib_getMapthing(lua_State *L)
{
int field;
+ if (gamestate != GS_LEVEL)
+ return luaL_error(L, "You cannot access this outside of a level!");
lua_settop(L, 2);
lua_remove(L, 1); // dummy userdata table is unused.
if (lua_isnumber(L, 1))
diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c
index d3e01a430..622309425 100644
--- a/src/lua_playerlib.c
+++ b/src/lua_playerlib.c
@@ -25,6 +25,8 @@
static int lib_iteratePlayers(lua_State *L)
{
INT32 i = -1;
+ if (gamestate != GS_LEVEL)
+ return luaL_error(L, "This function can only be used in a level!");
if (lua_gettop(L) < 2)
{
//return luaL_error(L, "Don't call players.iterate() directly, use it as 'for player in players.iterate do end'.");
@@ -51,6 +53,8 @@ static int lib_getPlayer(lua_State *L)
{
const char *field;
// i -> players[i]
+ if (gamestate != GS_LEVEL)
+ return luaL_error(L, "You cannot access this outside of a level!");
if (lua_type(L, 2) == LUA_TNUMBER)
{
lua_Integer i = luaL_checkinteger(L, 2);
@@ -122,8 +126,8 @@ static int player_get(lua_State *L)
lua_pushfixed(L, plr->bob);
else if (fastcmp(field,"aiming"))
lua_pushangle(L, plr->aiming);
- else if (fastcmp(field,"health"))
- lua_pushinteger(L, plr->health);
+ else if (fastcmp(field,"rings"))
+ lua_pushinteger(L, plr->rings);
else if (fastcmp(field,"pity"))
lua_pushinteger(L, plr->pity);
else if (fastcmp(field,"currentweapon"))
@@ -190,8 +194,6 @@ static int player_get(lua_State *L)
lua_pushinteger(L, plr->gotcontinue);
else if (fastcmp(field,"speed"))
lua_pushfixed(L, plr->speed);
- else if (fastcmp(field,"jumping"))
- lua_pushboolean(L, plr->jumping);
else if (fastcmp(field,"secondjump"))
lua_pushinteger(L, plr->secondjump);
else if (fastcmp(field,"fly1"))
@@ -382,8 +384,8 @@ static int player_set(lua_State *L)
else if (plr == &players[secondarydisplayplayer])
localaiming2 = plr->aiming;
}
- else if (fastcmp(field,"health"))
- plr->health = (INT32)luaL_checkinteger(L, 3);
+ else if (fastcmp(field,"rings"))
+ plr->rings = (INT32)luaL_checkinteger(L, 3);
else if (fastcmp(field,"pity"))
plr->pity = (SINT8)luaL_checkinteger(L, 3);
else if (fastcmp(field,"currentweapon"))
@@ -455,8 +457,6 @@ static int player_set(lua_State *L)
plr->gotcontinue = (UINT8)luaL_checkinteger(L, 3);
else if (fastcmp(field,"speed"))
plr->speed = luaL_checkfixed(L, 3);
- else if (fastcmp(field,"jumping"))
- plr->jumping = luaL_checkboolean(L, 3);
else if (fastcmp(field,"secondjump"))
plr->secondjump = (UINT8)luaL_checkinteger(L, 3);
else if (fastcmp(field,"fly1"))
diff --git a/src/lua_script.c b/src/lua_script.c
index acb306827..7bc9b88ec 100644
--- a/src/lua_script.c
+++ b/src/lua_script.c
@@ -48,6 +48,7 @@ static lua_CFunction liblist[] = {
LUA_SkinLib, // skin_t, skins[]
LUA_ThinkerLib, // thinker_t
LUA_MapLib, // line_t, side_t, sector_t, subsector_t
+ LUA_BlockmapLib, // blockmap stuff
LUA_HudLib, // HUD stuff
NULL
};
@@ -395,6 +396,7 @@ void LUA_InvalidateLevel(void)
{
thinker_t *th;
size_t i;
+ ffloor_t *rover = NULL;
if (!gL)
return;
@@ -406,7 +408,15 @@ void LUA_InvalidateLevel(void)
for (i = 0; i < numsubsectors; i++)
LUA_InvalidateUserdata(&subsectors[i]);
for (i = 0; i < numsectors; i++)
+ {
LUA_InvalidateUserdata(§ors[i]);
+ LUA_InvalidateUserdata(sectors[i].lines);
+ if (sectors[i].ffloors)
+ {
+ for (rover = sectors[i].ffloors; rover; rover = rover->next)
+ LUA_InvalidateUserdata(rover);
+ }
+ }
for (i = 0; i < numlines; i++)
{
LUA_InvalidateUserdata(&lines[i]);
@@ -416,6 +426,16 @@ void LUA_InvalidateLevel(void)
LUA_InvalidateUserdata(&sides[i]);
for (i = 0; i < numvertexes; i++)
LUA_InvalidateUserdata(&vertexes[i]);
+#ifdef HAVE_LUA_SEGS
+ for (i = 0; i < numsegs; i++)
+ LUA_InvalidateUserdata(&segs[i]);
+ for (i = 0; i < numnodes; i++)
+ {
+ LUA_InvalidateUserdata(&nodes[i]);
+ LUA_InvalidateUserdata(nodes[i].bbox);
+ LUA_InvalidateUserdata(nodes[i].children);
+ }
+#endif
}
void LUA_InvalidateMapthings(void)
@@ -455,6 +475,11 @@ enum
ARCH_SIDE,
ARCH_SUBSECTOR,
ARCH_SECTOR,
+#ifdef HAVE_LUA_SEGS
+ ARCH_SEG,
+ ARCH_NODE,
+#endif
+ ARCH_FFLOOR,
ARCH_MAPHEADER,
ARCH_TEND=0xFF,
@@ -474,6 +499,11 @@ static const struct {
{META_SIDE, ARCH_SIDE},
{META_SUBSECTOR,ARCH_SUBSECTOR},
{META_SECTOR, ARCH_SECTOR},
+#ifdef HAVE_LUA_SEGS
+ {META_SEG, ARCH_SEG},
+ {META_NODE, ARCH_NODE},
+#endif
+ {META_FFLOOR, ARCH_FFLOOR},
{META_MAPHEADER, ARCH_MAPHEADER},
{NULL, ARCH_NULL}
};
@@ -566,14 +596,14 @@ static UINT8 ArchiveValue(int TABLESINDEX, int myindex)
{
mobjinfo_t *info = *((mobjinfo_t **)lua_touserdata(gL, myindex));
WRITEUINT8(save_p, ARCH_MOBJINFO);
- WRITEUINT8(save_p, info - mobjinfo);
+ WRITEUINT16(save_p, info - mobjinfo);
break;
}
case ARCH_STATE:
{
state_t *state = *((state_t **)lua_touserdata(gL, myindex));
WRITEUINT8(save_p, ARCH_STATE);
- WRITEUINT8(save_p, state - states);
+ WRITEUINT16(save_p, state - states);
break;
}
case ARCH_MOBJ:
@@ -664,6 +694,56 @@ static UINT8 ArchiveValue(int TABLESINDEX, int myindex)
}
break;
}
+#ifdef HAVE_LUA_SEGS
+ case ARCH_SEG:
+ {
+ seg_t *seg = *((seg_t **)lua_touserdata(gL, myindex));
+ if (!seg)
+ WRITEUINT8(save_p, ARCH_NULL);
+ else {
+ WRITEUINT8(save_p, ARCH_SEG);
+ WRITEUINT16(save_p, seg - segs);
+ }
+ break;
+ }
+ case ARCH_NODE:
+ {
+ node_t *node = *((node_t **)lua_touserdata(gL, myindex));
+ if (!node)
+ WRITEUINT8(save_p, ARCH_NULL);
+ else {
+ WRITEUINT8(save_p, ARCH_NODE);
+ WRITEUINT16(save_p, node - nodes);
+ }
+ break;
+ }
+#endif
+ case ARCH_FFLOOR:
+ {
+ ffloor_t *rover = *((ffloor_t **)lua_touserdata(gL, myindex));
+ if (!rover)
+ WRITEUINT8(save_p, ARCH_NULL);
+ else {
+ ffloor_t *r2;
+ UINT16 i = 0;
+ // search for id
+ for (r2 = rover->target->ffloors; r2; r2 = r2->next)
+ {
+ if (r2 == rover)
+ break;
+ i++;
+ }
+ if (!r2)
+ WRITEUINT8(save_p, ARCH_NULL);
+ else
+ {
+ WRITEUINT8(save_p, ARCH_FFLOOR);
+ WRITEUINT16(save_p, rover->target - sectors);
+ WRITEUINT16(save_p, i);
+ }
+ }
+ break;
+ }
case ARCH_MAPHEADER:
{
mapheader_t *header = *((mapheader_t **)lua_touserdata(gL, myindex));
@@ -842,6 +922,23 @@ static UINT8 UnArchiveValue(int TABLESINDEX)
case ARCH_SECTOR:
LUA_PushUserdata(gL, §ors[READUINT16(save_p)], META_SECTOR);
break;
+#ifdef HAVE_LUA_SEGS
+ case ARCH_SEG:
+ LUA_PushUserdata(gL, &segs[READUINT16(save_p)], META_SEG);
+ break;
+ case ARCH_NODE:
+ LUA_PushUserdata(gL, &nodes[READUINT16(save_p)], META_NODE);
+ break;
+#endif
+ case ARCH_FFLOOR:
+ {
+ sector_t *sector = §ors[READUINT16(save_p)];
+ UINT16 id = READUINT16(save_p);
+ ffloor_t *rover = P_GetFFloorByID(sector, id);
+ if (rover)
+ LUA_PushUserdata(gL, rover, META_FFLOOR);
+ break;
+ }
case ARCH_MAPHEADER:
LUA_PushUserdata(gL, §ors[READUINT16(save_p)], META_MAPHEADER);
break;
diff --git a/src/lua_script.h b/src/lua_script.h
index 3b159234a..d143ed879 100644
--- a/src/lua_script.h
+++ b/src/lua_script.h
@@ -92,4 +92,7 @@ void COM_Lua_f(void);
}\
}
+// uncomment if you want seg_t/node_t in Lua
+// #define HAVE_LUA_SEGS
+
#endif
diff --git a/src/lua_thinkerlib.c b/src/lua_thinkerlib.c
index d5251425a..a661aaf04 100644
--- a/src/lua_thinkerlib.c
+++ b/src/lua_thinkerlib.c
@@ -16,84 +16,127 @@
#include "lua_script.h"
#include "lua_libs.h"
+#define META_ITERATIONSTATE "iteration state"
+
static const char *const iter_opt[] = {
"all",
"mobj",
NULL};
+static const actionf_p1 iter_funcs[] = {
+ NULL,
+ (actionf_p1)P_MobjThinker
+};
+
+struct iterationState {
+ actionf_p1 filter;
+ int next;
+};
+
+static int iterationState_gc(lua_State *L)
+{
+ struct iterationState *it = luaL_checkudata(L, -1, META_ITERATIONSTATE);
+ if (it->next != LUA_REFNIL)
+ {
+ luaL_unref(L, LUA_REGISTRYINDEX, it->next);
+ it->next = LUA_REFNIL;
+ }
+ return 0;
+}
+
+#define push_thinker(th) {\
+ if ((th)->function.acp1 == (actionf_p1)P_MobjThinker) \
+ LUA_PushUserdata(L, (th), META_MOBJ); \
+ else \
+ lua_pushlightuserdata(L, (th)); \
+}
+
static int lib_iterateThinkers(lua_State *L)
{
- int state = luaL_checkoption(L, 1, "mobj", iter_opt);
+ thinker_t *th = NULL, *next = NULL;
+ struct iterationState *it;
- thinker_t *th = NULL;
- actionf_p1 searchFunc;
- const char *searchMeta;
+ if (gamestate != GS_LEVEL)
+ return luaL_error(L, "This function can only be used in a level!");
+
+ it = luaL_checkudata(L, 1, META_ITERATIONSTATE);
lua_settop(L, 2);
- lua_remove(L, 1); // remove state now.
- switch(state)
+ if (lua_isnil(L, 2))
+ th = &thinkercap;
+ else if (lua_isuserdata(L, 2))
{
- case 0:
- searchFunc = NULL;
- searchMeta = NULL;
- break;
- case 1:
- default:
- searchFunc = (actionf_p1)P_MobjThinker;
- searchMeta = META_MOBJ;
- break;
+ if (lua_islightuserdata(L, 2))
+ th = lua_touserdata(L, 2);
+ else
+ {
+ th = *(thinker_t **)lua_touserdata(L, -1);
+ if (!th)
+ {
+ if (it->next == LUA_REFNIL)
+ return 0;
+
+ lua_rawgeti(L, LUA_REGISTRYINDEX, it->next);
+ if (lua_islightuserdata(L, -1))
+ next = lua_touserdata(L, -1);
+ else
+ next = *(thinker_t **)lua_touserdata(L, -1);
+ }
+ }
}
- if (!lua_isnil(L, 1)) {
- if (lua_islightuserdata(L, 1))
- th = (thinker_t *)lua_touserdata(L, 1);
- else if (searchMeta)
- th = *((thinker_t **)luaL_checkudata(L, 1, searchMeta));
- else
- th = *((thinker_t **)lua_touserdata(L, 1));
- } else
- th = &thinkercap;
+ luaL_unref(L, LUA_REGISTRYINDEX, it->next);
+ it->next = LUA_REFNIL;
- if (!th) // something got our userdata invalidated!
- return 0;
+ if (th && !next)
+ next = th->next;
+ if (!next)
+ return luaL_error(L, "next thinker invalidated during iteration");
- if (searchFunc == NULL)
- {
- if ((th = th->next) != &thinkercap)
+ for (; next != &thinkercap; next = next->next)
+ if (!it->filter || next->function.acp1 == it->filter)
{
- if (th->function.acp1 == (actionf_p1)P_MobjThinker)
- LUA_PushUserdata(L, th, META_MOBJ);
- else
- lua_pushlightuserdata(L, th);
+ push_thinker(next);
+ if (next->next != &thinkercap)
+ {
+ push_thinker(next->next);
+ it->next = luaL_ref(L, LUA_REGISTRYINDEX);
+ }
return 1;
}
- return 0;
- }
-
- for (th = th->next; th != &thinkercap; th = th->next)
- {
- if (th->function.acp1 != searchFunc)
- continue;
-
- LUA_PushUserdata(L, th, searchMeta);
- return 1;
- }
return 0;
}
static int lib_startIterate(lua_State *L)
{
- luaL_checkoption(L, 1, iter_opt[0], iter_opt);
- lua_pushcfunction(L, lib_iterateThinkers);
- lua_pushvalue(L, 1);
+ struct iterationState *it;
+
+ if (gamestate != GS_LEVEL)
+ return luaL_error(L, "This function can only be used in a level!");
+
+ lua_pushvalue(L, lua_upvalueindex(1));
+ it = lua_newuserdata(L, sizeof(struct iterationState));
+ luaL_getmetatable(L, META_ITERATIONSTATE);
+ lua_setmetatable(L, -2);
+
+ it->filter = iter_funcs[luaL_checkoption(L, 1, "mobj", iter_opt)];
+ it->next = LUA_REFNIL;
return 2;
}
+#undef push_thinker
+
int LUA_ThinkerLib(lua_State *L)
{
+ luaL_newmetatable(L, META_ITERATIONSTATE);
+ lua_pushcfunction(L, iterationState_gc);
+ lua_setfield(L, -2, "__gc");
+ lua_pop(L, 1);
+
lua_createtable(L, 0, 1);
- lua_pushcfunction(L, lib_startIterate);
+ lua_pushcfunction(L, lib_iterateThinkers);
+ lua_pushcclosure(L, lib_startIterate, 1);
lua_setfield(L, -2, "iterate");
lua_setglobal(L, "thinkers");
return 0;
diff --git a/src/m_aatree.h b/src/m_aatree.h
index c9077b974..eeaebca3b 100644
--- a/src/m_aatree.h
+++ b/src/m_aatree.h
@@ -28,4 +28,4 @@ void M_AATreeSet(aatree_t *aatree, INT32 key, void* value);
void *M_AATreeGet(aatree_t *aatree, INT32 key);
void M_AATreeIterate(aatree_t *aatree, aatree_iter_t callback);
-#endif
\ No newline at end of file
+#endif
diff --git a/src/m_anigif.c b/src/m_anigif.c
index 2540665ad..6ae112ea8 100644
--- a/src/m_anigif.c
+++ b/src/m_anigif.c
@@ -18,6 +18,7 @@
#include "z_zone.h"
#include "v_video.h"
#include "i_video.h"
+#include "m_misc.h"
// GIFs are always little-endian
#include "byteptr.h"
@@ -396,7 +397,6 @@ static void GIF_headwrite(void)
{
UINT8 *gifhead = Z_Malloc(800, PU_STATIC, NULL);
UINT8 *p = gifhead;
- RGBA_t *c;
INT32 i;
UINT16 rwidth, rheight;
@@ -427,12 +427,17 @@ static void GIF_headwrite(void)
WRITEUINT8(p, 0x00);
// write color table
- for (i = 0; i < 256; ++i)
{
- c = &pLocalPalette[i];
- WRITEUINT8(p, c->s.red);
- WRITEUINT8(p, c->s.green);
- WRITEUINT8(p, c->s.blue);
+ RGBA_t *pal = ((cv_screenshot_colorprofile.value)
+ ? pLocalPalette
+ : pMasterPalette);
+
+ for (i = 0; i < 256; i++)
+ {
+ WRITEUINT8(p, pal[i].s.red);
+ WRITEUINT8(p, pal[i].s.green);
+ WRITEUINT8(p, pal[i].s.blue);
+ }
}
// write extension block
diff --git a/src/m_cheat.c b/src/m_cheat.c
index 1c5f835cb..f988c0fd5 100644
--- a/src/m_cheat.c
+++ b/src/m_cheat.c
@@ -452,7 +452,7 @@ void Command_RTeleport_f(void)
else
inty = 0;
- ss = R_PointInSubsector(p->mo->x + intx*FRACUNIT, p->mo->y + inty*FRACUNIT);
+ ss = R_IsPointInSubsector(p->mo->x + intx*FRACUNIT, p->mo->y + inty*FRACUNIT);
if (!ss || ss->sector->ceilingheight - ss->sector->floorheight < p->mo->height)
{
CONS_Alert(CONS_NOTICE, M_GetText("Not a valid location.\n"));
@@ -606,7 +606,7 @@ void Command_CauseCfail_f(void)
players[consoleplayer].mo->y = 123311; //cfail cansuled kthxbye
players[consoleplayer].mo->z = 123311;
players[consoleplayer].score = 1337;
- players[consoleplayer].health = 1337;
+ players[consoleplayer].rings = 1337;
players[consoleplayer].mo->destscale = 25;
P_SetThingPosition(players[consoleplayer].mo);
@@ -720,7 +720,7 @@ void Command_Setrings_f(void)
if (COM_Argc() > 1)
{
// P_GivePlayerRings does value clamping
- players[consoleplayer].health = players[consoleplayer].mo->health = 1;
+ players[consoleplayer].rings = 0;
P_GivePlayerRings(&players[consoleplayer], atoi(COM_Argv(1)));
if (!G_IsSpecialStage(gamemap) || !useNightsSS)
players[consoleplayer].totalring -= atoi(COM_Argv(1)); //undo totalring addition done in P_GivePlayerRings
@@ -942,7 +942,7 @@ boolean OP_FreezeObjectplace(void)
if (!objectplacing)
return false;
- if ((maptol & TOL_NIGHTS) && (players[consoleplayer].pflags & PF_NIGHTSMODE))
+ if ((maptol & TOL_NIGHTS) && (players[consoleplayer].powers[pw_carry] == CR_NIGHTSMODE))
return false;
return true;
@@ -968,7 +968,7 @@ void OP_NightsObjectplace(player_t *player)
if (player->pflags & PF_ATTACKDOWN)
{
// Are ANY objectplace buttons pressed? If no, remove flag.
- if (!(cmd->buttons & (BT_ATTACK|BT_TOSSFLAG|BT_USE|BT_CAMRIGHT|BT_CAMLEFT)))
+ if (!(cmd->buttons & (BT_ATTACK|BT_TOSSFLAG|BT_USE|BT_WEAPONNEXT|BT_WEAPONPREV)))
player->pflags &= ~PF_ATTACKDOWN;
// Do nothing.
@@ -1019,7 +1019,7 @@ void OP_NightsObjectplace(player_t *player)
}
// This places a ring!
- if (cmd->buttons & BT_CAMRIGHT)
+ if (cmd->buttons & BT_WEAPONNEXT)
{
player->pflags |= PF_ATTACKDOWN;
if (!OP_HeightOkay(player, false))
@@ -1030,7 +1030,7 @@ void OP_NightsObjectplace(player_t *player)
}
// This places a wing item!
- if (cmd->buttons & BT_CAMLEFT)
+ if (cmd->buttons & BT_WEAPONPREV)
{
player->pflags |= PF_ATTACKDOWN;
if (!OP_HeightOkay(player, false))
@@ -1054,7 +1054,7 @@ void OP_NightsObjectplace(player_t *player)
if (!OP_HeightOkay(player, false))
return;
- if (player->mo->target->flags & MF_AMBUSH)
+ if (player->mo->target->flags2 & MF2_AMBUSH)
angle = (UINT16)player->anotherflyangle;
else
{
@@ -1165,19 +1165,20 @@ void OP_ObjectplaceMovement(player_t *player)
if (player->pflags & PF_ATTACKDOWN)
{
// Are ANY objectplace buttons pressed? If no, remove flag.
- if (!(cmd->buttons & (BT_ATTACK|BT_TOSSFLAG|BT_CAMRIGHT|BT_CAMLEFT)))
+ if (!(cmd->buttons & (BT_ATTACK|BT_TOSSFLAG|BT_WEAPONNEXT|BT_WEAPONPREV)))
player->pflags &= ~PF_ATTACKDOWN;
// Do nothing.
return;
}
- if (cmd->buttons & BT_CAMLEFT)
+ if (cmd->buttons & BT_WEAPONPREV)
{
OP_CycleThings(-1);
player->pflags |= PF_ATTACKDOWN;
}
- else if (cmd->buttons & BT_CAMRIGHT)
+
+ if (cmd->buttons & BT_WEAPONNEXT)
{
OP_CycleThings(1);
player->pflags |= PF_ATTACKDOWN;
@@ -1254,7 +1255,7 @@ void Command_ObjectPlace_f(void)
{
objectplacing = true;
- if ((players[0].pflags & PF_NIGHTSMODE))
+ if ((players[0].powers[pw_carry] == CR_NIGHTSMODE))
return;
if (!COM_CheckParm("-silent"))
@@ -1264,10 +1265,10 @@ void Command_ObjectPlace_f(void)
HU_DoCEcho(va(M_GetText(
"\\\\\\\\\\\\\\\\\\\\\\\\\x82"
" Objectplace Controls: \x80\\\\"
- "Camera L/R: Cycle mapthings\\"
- " Jump: Float up \\"
- " Spin: Float down \\"
- " Fire Ring: Place object \\")));
+ "Weapon Next/Prev: Cycle mapthings\\"
+ " Jump: Float up \\"
+ " Spin: Float down \\"
+ " Fire Ring: Place object \\")));
}
// Save all the player's data.
@@ -1297,7 +1298,7 @@ void Command_ObjectPlace_f(void)
// Like the classics, recover from death by entering objectplace
if (players[0].mo->health <= 0)
{
- players[0].mo->health = players[0].health = 1;
+ players[0].mo->health = 1;
players[0].deadtimer = 0;
op_oldflags1 = mobjinfo[MT_PLAYER].flags;
++players[0].lives;
@@ -1325,7 +1326,7 @@ void Command_ObjectPlace_f(void)
// Don't touch the NiGHTS Objectplace stuff.
// ... or if the mo mysteriously vanished.
- if (!players[0].mo || (players[0].pflags & PF_NIGHTSMODE))
+ if (!players[0].mo || (players[0].powers[pw_carry] == CR_NIGHTSMODE))
return;
// If still in dummy state, get out of it.
diff --git a/src/m_cond.c b/src/m_cond.c
index 3933815e5..b7735d4ce 100644
--- a/src/m_cond.c
+++ b/src/m_cond.c
@@ -565,7 +565,7 @@ extraemblem_t extraemblems[MAXEXTRAEMBLEMS] =
{"Game Complete", "Complete 1P Mode", 10, 'X', SKINCOLOR_BLUE, 0},
{"All Emeralds", "Complete 1P Mode with all Emeralds", 11, 'V', SKINCOLOR_GREY, 0},
{"Perfect Bonus", "Perfect Bonus on a non-secret stage", 30, 'P', SKINCOLOR_GOLD, 0},
- {"SRB1 Remake", "Complete SRB1 Remake", 21, 'O', SKINCOLOR_RUST, 0},
+ {"PLACEHOLDER", "PLACEHOLDER", 0, 'O', SKINCOLOR_RUST, 0},
{"NiGHTS Mastery", "Show your mastery of NiGHTS!", 22, 'W', SKINCOLOR_TEAL, 0},
};
@@ -573,31 +573,31 @@ extraemblem_t extraemblems[MAXEXTRAEMBLEMS] =
unlockable_t unlockables[MAXUNLOCKABLES] =
{
// Name, Objective, Menu Height, ConditionSet, Unlock Type, Variable, NoCecho, NoChecklist
- /* 01 */ {"Record Attack", "Complete Greenflower Zone, Act 1", 0, 1, SECRET_RECORDATTACK, 0, true, true, 0},
- /* 02 */ {"NiGHTS Mode", "Complete Floral Field", 0, 2, SECRET_NIGHTSMODE, 0, true, true, 0},
+ /* 01 */ {"Record Attack", "/", 0, 1, SECRET_RECORDATTACK, 0, true, true, 0},
+ /* 02 */ {"NiGHTS Mode", "/", 0, 2, SECRET_NIGHTSMODE, 0, true, true, 0},
- /* 03 */ {"Play Credits", "Complete 1P Mode", 30, 10, SECRET_CREDITS, 0, true, true, 0},
- /* 04 */ {"Sound Test", "Complete 1P Mode", 40, 10, SECRET_SOUNDTEST, 0, false, false, 0},
+ /* 03 */ {"Play Credits", "/", 30, 10, SECRET_CREDITS, 0, true, true, 0},
+ /* 04 */ {"Sound Test", "/", 40, 10, SECRET_SOUNDTEST, 0, false, false, 0},
- /* 05 */ {"EXTRA LEVELS", "", 60, 0, SECRET_HEADER, 0, true, true, 0},
+ /* 05 */ {"EXTRA LEVELS", "/", 58, 0, SECRET_HEADER, 0, true, true, 0},
- /* 06 */ {"Aerial Garden Zone", "Complete 1P Mode w/ all emeralds", 70, 11, SECRET_WARP, 40, false, false, 0},
- /* 07 */ {"Azure Temple Zone", "Complete Aerial Garden Zone", 80, 20, SECRET_WARP, 41, false, false, 0},
+ /* 06 */ {"Aerial Garden Zone", "/", 70, 11, SECRET_WARP, 40, false, false, 0},
+ /* 07 */ {"Azure Temple Zone", "/", 80, 20, SECRET_WARP, 41, false, false, 0},
- /* 08 */ {"BONUS LEVELS", "", 100, 0, SECRET_HEADER, 0, true, true, 0},
+ /* 08 */ {"BONUS LEVELS", "/", 98, 0, SECRET_HEADER, 0, true, true, 0},
- /* 09 */ {"SRB1 Remake", "Collect 20 Emblems", 130, 40, SECRET_WARP, 101, false, false, 0},
- /* 10 */ {"Mario Koopa Blast", "Collect 60 Emblems", 110, 42, SECRET_WARP, 30, false, false, 0},
- /* 11 */ {"SRB1 Level Select", "Complete SRB1 Remake", 140, 21, SECRET_LEVELSELECT, 2, false, true, 0},
+ /* 09 */ {"PLACEHOLDER", "/", 0, 0, SECRET_NONE, 0, true, true, 0},
+ /* 10 */ {"Mario Koopa Blast", "/", 110, 42, SECRET_WARP, 30, false, false, 0},
+ /* 11 */ {"PLACEHOLDER", "/", 0, 0, SECRET_NONE, 0, true, true, 0},
- /* 12 */ {"Spring Hill Zone", "Collect 100 Emblems", 0, 44, SECRET_NONE, 0, false, false, 0},
- /* 13 */ {"Black Hole", "A Rank in all Special Stages", 0, 50, SECRET_NONE, 0, false, true, 0},
+ /* 12 */ {"Spring Hill Zone", "/", 0, 44, SECRET_NONE, 0, false, false, 0},
+ /* 13 */ {"Black Hole", "Get grade A in all Special Stages", 0, 50, SECRET_NONE, 0, false, true, 0},
- /* 14 */ {"Emblem Hints", "Collect 40 Emblems", 0, 41, SECRET_EMBLEMHINTS, 0, false, true, 0},
- /* 15 */ {"Emblem Radar", "Collect 80 Emblems", 0, 43, SECRET_ITEMFINDER, 0, false, true, 0},
+ /* 14 */ {"Emblem Hints", "/", 0, 41, SECRET_EMBLEMHINTS, 0, false, true, 0},
+ /* 15 */ {"Emblem Radar", "/", 0, 43, SECRET_ITEMFINDER, 0, false, true, 0},
- /* 16 */ {"Pandora's Box", "Collect All Emblems", 0, 45, SECRET_PANDORA, 0, false, false, 0},
- /* 17 */ {"Level Select", "Collect All Emblems", 20, 45, SECRET_LEVELSELECT, 1, false, true, 0},
+ /* 16 */ {"Pandora's Box", "/", 0, 45, SECRET_PANDORA, 0, false, false, 0},
+ /* 17 */ {"Level Select", "/", 20, 45, SECRET_LEVELSELECT, 1, false, true, 0},
};
// Default number of emblems and extra emblems
@@ -624,9 +624,6 @@ void M_SetupDefaultConditionSets(void)
// -- 20: Beat AGZ
M_AddRawCondition(20, 1, UC_MAPBEATEN, 40, 0, 0);
- // -- 21: Beat SRB1 Remake
- M_AddRawCondition(21, 1, UC_MAPBEATEN, 132, 0, 0);
-
// -- 22: Beat Black Hole
M_AddRawCondition(22, 1, UC_MAPBEATEN, 57, 0, 0);
@@ -932,7 +929,7 @@ UINT8 M_CheckLevelEmblems(void)
// Update Score, Time, Rings emblems
for (i = 0; i < numemblems; ++i)
{
- if (emblemlocations[i].type <= ET_SKIN || emblemlocations[i].collected)
+ if (emblemlocations[i].type <= ET_SKIN || emblemlocations[i].type == ET_MAP || emblemlocations[i].collected)
continue;
levelnum = emblemlocations[i].level;
@@ -966,6 +963,42 @@ UINT8 M_CheckLevelEmblems(void)
return somethingUnlocked;
}
+UINT8 M_CompletionEmblems(void) // Bah! Duplication sucks, but it's for a separate print when awarding emblems and it's sorta different enough.
+{
+ INT32 i;
+ INT32 embtype;
+ INT16 levelnum;
+ UINT8 res;
+ UINT8 somethingUnlocked = 0;
+ UINT8 flags;
+
+ for (i = 0; i < numemblems; ++i)
+ {
+ if (emblemlocations[i].type != ET_MAP || emblemlocations[i].collected)
+ continue;
+
+ levelnum = emblemlocations[i].level;
+ embtype = emblemlocations[i].var;
+ flags = MV_BEATEN;
+
+ if (embtype & ME_ALLEMERALDS)
+ flags |= MV_ALLEMERALDS;
+
+ if (embtype & ME_ULTIMATE)
+ flags |= MV_ULTIMATE;
+
+ if (embtype & ME_PERFECT)
+ flags |= MV_PERFECT;
+
+ res = ((mapvisited[levelnum - 1] & flags) == flags);
+
+ emblemlocations[i].collected = res;
+ if (res)
+ ++somethingUnlocked;
+ }
+ return somethingUnlocked;
+}
+
// -------------------
// Quick unlock checks
// -------------------
diff --git a/src/m_cond.h b/src/m_cond.h
index e61ff1f79..94802f665 100644
--- a/src/m_cond.h
+++ b/src/m_cond.h
@@ -73,6 +73,12 @@ typedef struct
#define ET_RINGS 4
#define ET_NGRADE 5
#define ET_NTIME 6
+#define ET_MAP 7
+
+// Map emblem flags
+#define ME_ALLEMERALDS 1
+#define ME_ULTIMATE 2
+#define ME_PERFECT 4
typedef struct
{
@@ -153,6 +159,7 @@ void M_CheckUnlockConditions(void);
UINT8 M_UpdateUnlockablesAndExtraEmblems(void);
void M_SilentUpdateUnlockablesAndEmblems(void);
UINT8 M_CheckLevelEmblems(void);
+UINT8 M_CompletionEmblems(void);
// Checking unlockable status
UINT8 M_AnySecretUnlocked(void);
diff --git a/src/m_fixed.h b/src/m_fixed.h
index 70402f27a..1cf9abbae 100644
--- a/src/m_fixed.h
+++ b/src/m_fixed.h
@@ -46,41 +46,6 @@ typedef INT32 fixed_t;
#define FLOAT_TO_FIXED(f) (fixed_t)((f) * ((float)FRACUNIT))
-/** \brief The TMulScale16 function
-
- \param a a parameter of type fixed_t
- \param b a parameter of type fixed_t
- \param c a parameter of type fixed_t
- \param d a parameter of type fixed_t
- \param e a parameter of type fixed_t
- \param f a parameter of type fixed_t
-
- \return fixed_t
-
-
-*/
-FUNCMATH FUNCINLINE static ATTRINLINE fixed_t TMulScale16(fixed_t a, fixed_t b, fixed_t c, fixed_t d, fixed_t e, fixed_t f) \
-{ \
- return (fixed_t)((((INT64)a * (INT64)b) + ((INT64)c * (INT64)d) \
- + ((INT64)e * (INT64)f)) >> 16); \
-}
-
-/** \brief The DMulScale16 function
-
- \param a a parameter of type fixed_t
- \param b a parameter of type fixed_t
- \param c a parameter of type fixed_t
- \param d a parameter of type fixed_t
-
- \return fixed_t
-
-
-*/
-FUNCMATH FUNCINLINE static ATTRINLINE fixed_t DMulScale16(fixed_t a, fixed_t b, fixed_t c, fixed_t d) \
-{ \
- return (fixed_t)((((INT64)a * (INT64)b) + ((INT64)c * (INT64)d)) >> 16); \
-}
-
#if defined (__WATCOMC__) && FRACBITS == 16
#pragma aux FixedMul = \
"imul ebx", \
@@ -283,9 +248,16 @@ FUNCMATH FUNCINLINE static ATTRINLINE fixed_t FixedFloor(fixed_t x)
{
const fixed_t a = abs(x); //absolute of x
const fixed_t i = (a>>FRACBITS)< 0)
+ return x-f;
+ else
+ return x-(FRACUNIT-f);
+ }
return INT32_MIN;
}
@@ -301,7 +273,7 @@ FUNCMATH FUNCINLINE static ATTRINLINE fixed_t FixedTrunc(fixed_t x)
{
const fixed_t a = abs(x); //absolute of x
const fixed_t i = (a>>FRACBITS)< 0)
@@ -324,11 +296,18 @@ FUNCMATH FUNCINLINE static ATTRINLINE fixed_t FixedCeil(fixed_t x)
{
const fixed_t a = abs(x); //absolute of x
const fixed_t i = (a>>FRACBITS)< 0)
+ return x+(FRACUNIT-f);
+ else
+ return x+f;
+ }
return INT32_MAX;
}
@@ -344,7 +323,9 @@ FUNCMATH FUNCINLINE static ATTRINLINE fixed_t FixedRound(fixed_t x)
{
const fixed_t a = abs(x); //absolute of x
const fixed_t i = (a>>FRACBITS)<typeoflevel & TOL_COOP)) ||
- (cv_newgametype.value == GT_COMPETITION && !(mapheaderinfo[cv_nextmap.value-1]->typeoflevel & TOL_COMPETITION)) ||
- (cv_newgametype.value == GT_RACE && !(mapheaderinfo[cv_nextmap.value-1]->typeoflevel & TOL_RACE)) ||
- ((cv_newgametype.value == GT_MATCH || cv_newgametype.value == GT_TEAMMATCH) && !(mapheaderinfo[cv_nextmap.value-1]->typeoflevel & TOL_MATCH)) ||
- ((cv_newgametype.value == GT_TAG || cv_newgametype.value == GT_HIDEANDSEEK) && !(mapheaderinfo[cv_nextmap.value-1]->typeoflevel & TOL_TAG)) ||
- (cv_newgametype.value == GT_CTF && !(mapheaderinfo[cv_nextmap.value-1]->typeoflevel & TOL_CTF)))
+ if (!M_CanShowLevelOnPlatter(cv_nextmap.value-1, cv_newgametype.value))
{
INT32 value = 0;
@@ -1932,9 +2054,7 @@ static void Newgametype_OnChange(void)
break;
}
- CV_SetValue(&cv_nextmap, M_FindFirstMap(value));
- CV_AddValue(&cv_nextmap, -1);
- CV_AddValue(&cv_nextmap, 1);
+ CV_SetValue(&cv_nextmap, M_GetFirstLevelInList(value));
}
}
}
@@ -1979,6 +2099,34 @@ menu_t *currentMenu = &MainDef;
// BASIC MENU HANDLING
// =========================================================================
+static void M_GoBack(INT32 choice)
+{
+ (void)choice;
+
+ if (currentMenu->prevMenu)
+ {
+ //If we entered the game search menu, but didn't enter a game,
+ //make sure the game doesn't still think we're in a netgame.
+ if (!Playing() && netgame && multiplayer)
+ {
+ MSCloseUDPSocket(); // Clean up so we can re-open the connection later.
+ netgame = false;
+ multiplayer = false;
+ }
+
+ if ((currentMenu->prevMenu == &MainDef) && (currentMenu == &SP_TimeAttackDef || currentMenu == &SP_NightsAttackDef))
+ {
+ // D_StartTitle does its own wipe, since GS_TIMEATTACK is now a complete gamestate.
+ menuactive = false;
+ D_StartTitle();
+ }
+ else
+ M_SetupNextMenu(currentMenu->prevMenu);
+ }
+ else
+ M_ClearMenus(true);
+}
+
static void M_ChangeCvar(INT32 choice)
{
consvar_t *cv = (consvar_t *)currentMenu->menuitems[itemOn].itemaction;
@@ -2034,11 +2182,22 @@ static boolean M_ChangeStringCvar(INT32 choice)
return false;
}
+// resets all cvars on a menu - assumes that all that have itemactions are cvars
+static void M_ResetCvars(void)
+{
+ INT32 i;
+ consvar_t *cv;
+ for (i = 0; i < currentMenu->numitems; i++)
+ {
+ if (!(currentMenu->menuitems[i].status & IT_CVAR) || !(cv = (consvar_t *)currentMenu->menuitems[i].itemaction))
+ continue;
+ CV_SetValue(cv, atoi(cv->defaultvalue));
+ }
+}
+
static void M_NextOpt(void)
{
INT16 oldItemOn = itemOn; // prevent infinite loop
- lastdirection = true;
-
do
{
if (itemOn + 1 > currentMenu->numitems - 1)
@@ -2051,8 +2210,6 @@ static void M_NextOpt(void)
static void M_PrevOpt(void)
{
INT16 oldItemOn = itemOn; // prevent infinite loop
- lastdirection = false;
-
do
{
if (!itemOn)
@@ -2083,11 +2240,6 @@ boolean M_Responder(event_t *ev)
|| gamestate == GS_CREDITS || gamestate == GS_EVALUATION)
return false;
- if (ev->type == ev_keyup && (ev->data1 == KEY_LSHIFT || ev->data1 == KEY_RSHIFT))
- {
- shiftdown = false;
- return false;
- }
if (noFurtherInput)
{
// Ignore input after enter/escape/other buttons
@@ -2101,10 +2253,6 @@ boolean M_Responder(event_t *ev)
// added 5-2-98 remap virtual keys (mouse & joystick buttons)
switch (ch)
{
- case KEY_LSHIFT:
- case KEY_RSHIFT:
- shiftdown = true;
- break; //return false;
case KEY_MOUSE1:
case KEY_JOY1:
case KEY_JOY1 + 2:
@@ -2251,7 +2399,7 @@ boolean M_Responder(event_t *ev)
return true;
case KEY_F11: // Gamma Level
- CV_AddValue(&cv_usegamma, 1);
+ CV_AddValue(&cv_globalgamma, 1);
return true;
// Spymode on F12 handled in game logic
@@ -2332,7 +2480,7 @@ boolean M_Responder(event_t *ev)
case KEY_DOWNARROW:
M_NextOpt();
S_StartSound(NULL, sfx_menu1);
- if (currentMenu == &SP_PlayerDef)
+ if (currentMenu == &MISC_ChangeGameTypeDef)
{
Z_Free(char_notes);
char_notes = NULL;
@@ -2342,7 +2490,7 @@ boolean M_Responder(event_t *ev)
case KEY_UPARROW:
M_PrevOpt();
S_StartSound(NULL, sfx_menu1);
- if (currentMenu == &SP_PlayerDef)
+ if (currentMenu == &MISC_ChangeGameTypeDef)
{
Z_Free(char_notes);
char_notes = NULL;
@@ -2353,8 +2501,7 @@ boolean M_Responder(event_t *ev)
if (routine && ((currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_ARROWS
|| (currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_CVAR))
{
- if (currentMenu != &OP_SoundOptionsDef)
- S_StartSound(NULL, sfx_menu1);
+ S_StartSound(NULL, sfx_menu1);
routine(0);
}
return true;
@@ -2363,8 +2510,7 @@ boolean M_Responder(event_t *ev)
if (routine && ((currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_ARROWS
|| (currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_CVAR))
{
- if (currentMenu != &OP_SoundOptionsDef)
- S_StartSound(NULL, sfx_menu1);
+ S_StartSound(NULL, sfx_menu1);
routine(1);
}
return true;
@@ -2406,28 +2552,8 @@ boolean M_Responder(event_t *ev)
case KEY_ESCAPE:
noFurtherInput = true;
currentMenu->lastOn = itemOn;
- if (currentMenu->prevMenu)
- {
- //If we entered the game search menu, but didn't enter a game,
- //make sure the game doesn't still think we're in a netgame.
- if (!Playing() && netgame && multiplayer)
- {
- MSCloseUDPSocket(); // Clean up so we can re-open the connection later.
- netgame = false;
- multiplayer = false;
- }
- if (currentMenu == &SP_TimeAttackDef || currentMenu == &SP_NightsAttackDef)
- {
- // D_StartTitle does its own wipe, since GS_TIMEATTACK is now a complete gamestate.
- menuactive = false;
- D_StartTitle();
- }
- else
- M_SetupNextMenu(currentMenu->prevMenu);
- }
- else
- M_ClearMenus(true);
+ M_GoBack(0);
return true;
@@ -2436,6 +2562,7 @@ boolean M_Responder(event_t *ev)
{
// detach any keys associated with the game control
G_ClearControlKeys(setupcontrols, currentMenu->menuitems[itemOn].alphaKey);
+ S_StartSound(NULL, sfx_shldls);
return true;
}
// Why _does_ backspace go back anyway?
@@ -2599,10 +2726,14 @@ void M_StartControlPanel(void)
}
if (splitscreen)
+ {
MPauseMenu[mpause_psetupsplit].status = MPauseMenu[mpause_psetupsplit2].status = IT_STRING | IT_CALL;
+ MPauseMenu[mpause_psetup].text = "Player 1 Setup";
+ }
else
{
MPauseMenu[mpause_psetup].status = IT_STRING | IT_CALL;
+ MPauseMenu[mpause_psetup].text = "Player Setup";
if (G_GametypeHasTeams())
MPauseMenu[mpause_switchteam].status = IT_STRING | IT_SUBMENU;
@@ -2751,15 +2882,14 @@ void M_Init(void)
#ifdef HWRENDER
// Permanently hide some options based on render mode
if (rendermode == render_soft)
- OP_VideoOptionsMenu[1].status = IT_DISABLED;
+ OP_VideoOptionsMenu[4].status = IT_DISABLED;
+ else if (rendermode == render_opengl)
+ OP_ScreenshotOptionsMenu[op_screenshot_colorprofile].status = IT_GRAYEDOUT;
#endif
#ifndef NONET
CV_RegisterVar(&cv_serversort);
#endif
-
- //todo put this somewhere better...
- CV_RegisterVar(&cv_allcaps);
}
// ==========================================================================
@@ -2824,30 +2954,44 @@ static void M_DrawSlider(INT32 x, INT32 y, const consvar_t *cv)
INT32 range;
patch_t *p;
+ x = BASEVIDWIDTH - x - SLIDER_WIDTH;
+
+ V_DrawScaledPatch(x, y, 0, W_CachePatchName("M_SLIDEL", PU_CACHE));
+
+ p = W_CachePatchName("M_SLIDEM", PU_CACHE);
+ for (i = 1; i < SLIDER_RANGE; i++)
+ V_DrawScaledPatch (x+i*8, y, 0,p);
+
+ p = W_CachePatchName("M_SLIDER", PU_CACHE);
+ V_DrawScaledPatch(x+i*8, y, 0, p);
+
+ // draw the slider cursor
+ p = W_CachePatchName("M_SLIDEC", PU_CACHE);
+
for (i = 0; cv->PossibleValue[i+1].strvalue; i++);
+ if ((range = atoi(cv->defaultvalue)) != cv->value)
+ {
+ range = ((range - cv->PossibleValue[0].value) * 100 /
+ (cv->PossibleValue[i].value - cv->PossibleValue[0].value));
+
+ if (range < 0)
+ range = 0;
+ else if (range > 100)
+ range = 100;
+
+ V_DrawMappedPatch(x + 2 + (SLIDER_RANGE*8*range)/100, y, V_TRANSLUCENT, p, yellowmap);
+ }
+
range = ((cv->value - cv->PossibleValue[0].value) * 100 /
(cv->PossibleValue[i].value - cv->PossibleValue[0].value));
if (range < 0)
range = 0;
- if (range > 100)
+ else if (range > 100)
range = 100;
- x = BASEVIDWIDTH - x - SLIDER_WIDTH;
-
- V_DrawScaledPatch(x - 8, y, 0, W_CachePatchName("M_SLIDEL", PU_CACHE));
-
- p = W_CachePatchName("M_SLIDEM", PU_CACHE);
- for (i = 0; i < SLIDER_RANGE; i++)
- V_DrawScaledPatch (x+i*8, y, 0,p);
-
- p = W_CachePatchName("M_SLIDER", PU_CACHE);
- V_DrawScaledPatch(x+SLIDER_RANGE*8, y, 0, p);
-
- // draw the slider cursor
- p = W_CachePatchName("M_SLIDEC", PU_CACHE);
- V_DrawMappedPatch(x + ((SLIDER_RANGE-1)*8*range)/100, y, 0, p, yellowmap);
+ V_DrawMappedPatch(x + 2 + (SLIDER_RANGE*8*range)/100, y, 0, p, yellowmap);
}
//
@@ -2952,6 +3096,8 @@ static void M_DrawMapEmblems(INT32 mapnum, INT32 x, INT32 y)
curtype = 1; break;
case ET_NGRADE: case ET_NTIME:
curtype = 2; break;
+ case ET_MAP:
+ curtype = 3; break;
default:
curtype = 0; break;
}
@@ -3078,7 +3224,7 @@ static void M_DrawGenericMenu(void)
y += 16;
break;
default:
- V_DrawString(BASEVIDWIDTH - x - V_StringWidth(cv->string, 0), y,
+ V_DrawRightAlignedString(BASEVIDWIDTH - x, y,
((cv->flags & CV_CHEAT) && !CV_IsSetToDefault(cv) ? V_REDMAP : V_YELLOWMAP), cv->string);
break;
}
@@ -3115,7 +3261,8 @@ static void M_DrawGenericMenu(void)
if (currentMenu->menuitems[i].alphaKey)
y = currentMenu->y+currentMenu->menuitems[i].alphaKey;
- V_DrawString(x-16, y, V_YELLOWMAP, currentMenu->menuitems[i].text);
+ //V_DrawString(x-16, y, V_YELLOWMAP, currentMenu->menuitems[i].text);
+ M_DrawLevelPlatterHeader(y - (lsheadingheight - 12), currentMenu->menuitems[i].text, true);
y += SMALLLINEHEIGHT;
break;
}
@@ -3136,6 +3283,135 @@ static void M_DrawGenericMenu(void)
}
}
+#define scrollareaheight 72
+
+// note that alphakey is multiplied by 2 for scrolling menus to allow greater usage in UINT8 range.
+static void M_DrawGenericScrollMenu(void)
+{
+ INT32 x, y, i, max, bottom, tempcentery, cursory = 0;
+
+ // DRAW MENU
+ x = currentMenu->x;
+ y = currentMenu->y;
+
+ if ((currentMenu->menuitems[itemOn].alphaKey*2 - currentMenu->menuitems[0].alphaKey*2) <= scrollareaheight)
+ tempcentery = currentMenu->y - currentMenu->menuitems[0].alphaKey*2;
+ else if ((currentMenu->menuitems[currentMenu->numitems-1].alphaKey*2 - currentMenu->menuitems[itemOn].alphaKey*2) <= scrollareaheight)
+ tempcentery = currentMenu->y - currentMenu->menuitems[currentMenu->numitems-1].alphaKey*2 + 2*scrollareaheight;
+ else
+ tempcentery = currentMenu->y - currentMenu->menuitems[itemOn].alphaKey*2 + scrollareaheight;
+
+ for (i = 0; i < currentMenu->numitems; i++)
+ {
+ if (currentMenu->menuitems[i].status != IT_DISABLED && currentMenu->menuitems[i].alphaKey*2 + tempcentery >= currentMenu->y)
+ break;
+ }
+
+ for (bottom = currentMenu->numitems; bottom > 0; bottom--)
+ {
+ if (currentMenu->menuitems[bottom-1].status != IT_DISABLED)
+ break;
+ }
+
+ for (max = bottom; max > 0; max--)
+ {
+ if (currentMenu->menuitems[max-1].status != IT_DISABLED && currentMenu->menuitems[max-1].alphaKey*2 + tempcentery <= (currentMenu->y + 2*scrollareaheight))
+ break;
+ }
+
+ if (i)
+ V_DrawString(currentMenu->x - 20, currentMenu->y, V_YELLOWMAP, "\x1A"); // up arrow
+ if (max != bottom)
+ V_DrawString(currentMenu->x - 20, currentMenu->y + 2*scrollareaheight, V_YELLOWMAP, "\x1B"); // down arrow
+
+ // draw title (or big pic)
+ M_DrawMenuTitle();
+
+ for (; i < max; i++)
+ {
+ y = currentMenu->menuitems[i].alphaKey*2 + tempcentery;
+ if (i == itemOn)
+ cursory = y;
+ switch (currentMenu->menuitems[i].status & IT_DISPLAY)
+ {
+ case IT_PATCH:
+ case IT_DYBIGSPACE:
+ case IT_BIGSLIDER:
+ case IT_STRING2:
+ case IT_DYLITLSPACE:
+ case IT_GRAYPATCH:
+ case IT_TRANSTEXT2:
+ // unsupported
+ break;
+ case IT_NOTHING:
+ break;
+ case IT_STRING:
+ case IT_WHITESTRING:
+ if (i != itemOn && (currentMenu->menuitems[i].status & IT_DISPLAY)==IT_STRING)
+ V_DrawString(x, y, 0, currentMenu->menuitems[i].text);
+ else
+ V_DrawString(x, y, V_YELLOWMAP, currentMenu->menuitems[i].text);
+
+ // Cvar specific handling
+ switch (currentMenu->menuitems[i].status & IT_TYPE)
+ case IT_CVAR:
+ {
+ consvar_t *cv = (consvar_t *)currentMenu->menuitems[i].itemaction;
+ switch (currentMenu->menuitems[i].status & IT_CVARTYPE)
+ {
+ case IT_CV_SLIDER:
+ M_DrawSlider(x, y, cv);
+ case IT_CV_NOPRINT: // color use this
+ case IT_CV_INVISSLIDER: // monitor toggles use this
+ break;
+ case IT_CV_STRING:
+#if 1
+ if (y + 12 > (currentMenu->y + 2*scrollareaheight))
+ break;
+ M_DrawTextBox(x, y + 4, MAXSTRINGLENGTH, 1);
+ V_DrawString(x + 8, y + 12, V_ALLOWLOWERCASE, cv->string);
+ if (skullAnimCounter < 4 && i == itemOn)
+ V_DrawCharacter(x + 8 + V_StringWidth(cv->string, 0), y + 12,
+ '_' | 0x80, false);
+#else // cool new string type stuff, not ready for limelight
+ if (i == itemOn)
+ {
+ V_DrawFill(x-2, y-1, MAXSTRINGLENGTH*8 + 4, 8+3, 159);
+ V_DrawString(x, y, V_ALLOWLOWERCASE, cv->string);
+ if (skullAnimCounter < 4)
+ V_DrawCharacter(x + V_StringWidth(cv->string, 0), y, '_' | 0x80, false);
+ }
+ else
+ V_DrawRightAlignedString(BASEVIDWIDTH - x, y,
+ V_YELLOWMAP|V_ALLOWLOWERCASE, cv->string);
+#endif
+ break;
+ default:
+ V_DrawRightAlignedString(BASEVIDWIDTH - x, y,
+ ((cv->flags & CV_CHEAT) && !CV_IsSetToDefault(cv) ? V_REDMAP : V_YELLOWMAP), cv->string);
+ break;
+ }
+ break;
+ }
+ break;
+ case IT_TRANSTEXT:
+ V_DrawString(x, y, V_TRANSLUCENT, currentMenu->menuitems[i].text);
+ break;
+ case IT_QUESTIONMARKS:
+ V_DrawString(x, y, V_TRANSLUCENT|V_OLDSPACING, M_CreateSecretMenuOption(currentMenu->menuitems[i].text));
+ break;
+ case IT_HEADERTEXT:
+ //V_DrawString(x-16, y, V_YELLOWMAP, currentMenu->menuitems[i].text);
+ M_DrawLevelPlatterHeader(y - (lsheadingheight - 12), currentMenu->menuitems[i].text, true);
+ break;
+ }
+ }
+
+ // DRAW THE SKULL CURSOR
+ V_DrawScaledPatch(currentMenu->x - 24, cursory, 0,
+ W_CachePatchName("M_CURSOR", PU_CACHE));
+}
+
static void M_DrawPauseMenu(void)
{
if (!netgame && !multiplayer && (gamestate == GS_LEVEL || gamestate == GS_INTERMISSION))
@@ -3429,7 +3705,7 @@ static void M_PatchSkinNameTable(void)
for (j = 0; j < MAXSKINS; j++)
{
- if (skins[j].name[0] != '\0' && R_SkinUnlock(j))
+ if (skins[j].name[0] != '\0' && R_SkinUsable(-1, j))
{
skins_cons_t[j].strvalue = skins[j].realname;
skins_cons_t[j].value = j+1;
@@ -3441,31 +3717,50 @@ static void M_PatchSkinNameTable(void)
}
}
- CV_SetValue(&cv_chooseskin, cv_chooseskin.value); // This causes crash sometimes?!
-
CV_SetValue(&cv_chooseskin, 1);
- CV_AddValue(&cv_chooseskin, -1);
- CV_AddValue(&cv_chooseskin, 1);
+ Nextmap_OnChange();
return;
}
-// Call before showing any level-select menus
-static void M_PrepareLevelSelect(void)
+//
+// M_LevelAvailableOnPlatter
+//
+// Okay, you know that the level SHOULD show up on the platter already.
+// The only question is whether it should be as a question mark,
+// (hinting as to its existence), or as its pure, unfettered self.
+//
+static boolean M_LevelAvailableOnPlatter(INT32 mapnum)
{
- if (levellistmode != LLM_CREATESERVER)
- CV_SetValue(&cv_nextmap, M_GetFirstLevelInList());
- else
- Newgametype_OnChange(); // Make sure to start on an appropriate map if wads have been added
+ if (M_MapLocked(mapnum+1))
+ return false; // not unlocked
+
+ switch (levellistmode)
+ {
+ case LLM_RECORDATTACK:
+ case LLM_NIGHTSATTACK:
+ if (mapheaderinfo[mapnum]->menuflags & LF2_NOVISITNEEDED)
+ return true;
+
+ if (!mapvisited[mapnum])
+ return false;
+
+ // intentional fallthrough
+ case LLM_CREATESERVER:
+ case LLM_LEVELSELECT:
+ default:
+ return true;
+ }
+ return true;
}
//
-// M_CanShowLevelInList
+// M_CanShowLevelOnPlatter
//
// Determines whether to show a given map in the various level-select lists.
// Set gt = -1 to ignore gametype.
//
-boolean M_CanShowLevelInList(INT32 mapnum, INT32 gt)
+static boolean M_CanShowLevelOnPlatter(INT32 mapnum, INT32 gt)
{
// Does the map exist?
if (!mapheaderinfo[mapnum])
@@ -3475,6 +3770,9 @@ boolean M_CanShowLevelInList(INT32 mapnum, INT32 gt)
if (!mapheaderinfo[mapnum]->lvlttl[0])
return false;
+ /*if (M_MapLocked(mapnum+1))
+ return false; // not unlocked*/
+
switch (levellistmode)
{
case LLM_CREATESERVER:
@@ -3482,9 +3780,6 @@ boolean M_CanShowLevelInList(INT32 mapnum, INT32 gt)
if (mapheaderinfo[mapnum]->menuflags & LF2_HIDEINMENU)
return false;
- if (M_MapLocked(mapnum+1))
- return false; // not unlocked
-
if (gt == GT_COOP && (mapheaderinfo[mapnum]->typeoflevel & TOL_COOP))
return true;
@@ -3509,37 +3804,16 @@ boolean M_CanShowLevelInList(INT32 mapnum, INT32 gt)
if (mapheaderinfo[mapnum]->levelselect != maplistoption)
return false;
- if (M_MapLocked(mapnum+1))
- return false; // not unlocked
-
return true;
case LLM_RECORDATTACK:
if (!(mapheaderinfo[mapnum]->menuflags & LF2_RECORDATTACK))
return false;
- if (M_MapLocked(mapnum+1))
- return false; // not unlocked
-
- if (mapheaderinfo[mapnum]->menuflags & LF2_NOVISITNEEDED)
- return true;
-
- if (!mapvisited[mapnum])
- return false;
-
return true;
case LLM_NIGHTSATTACK:
if (!(mapheaderinfo[mapnum]->menuflags & LF2_NIGHTSATTACK))
return false;
- if (M_MapLocked(mapnum+1))
- return false; // not unlocked
-
- if (mapheaderinfo[mapnum]->menuflags & LF2_NOVISITNEEDED)
- return true;
-
- if (!mapvisited[mapnum])
- return false;
-
return true;
}
@@ -3547,23 +3821,541 @@ boolean M_CanShowLevelInList(INT32 mapnum, INT32 gt)
return false;
}
-static INT32 M_CountLevelsToShowInList(void)
+#if 0
+static INT32 M_CountLevelsToShowOnPlatter(INT32 gt)
{
INT32 mapnum, count = 0;
for (mapnum = 0; mapnum < NUMMAPS; mapnum++)
- if (M_CanShowLevelInList(mapnum, -1))
+ if (M_CanShowLevelOnPlatter(mapnum, gt))
count++;
return count;
}
+#endif
-static INT32 M_GetFirstLevelInList(void)
+#if 0
+static boolean M_SetNextMapOnPlatter(void)
+{
+ INT32 row, col = 0;
+ while (col < 3)
+ {
+ row = 0;
+ while (row < levelselect.numrows)
+ {
+ if (levelselect.rows[row].maplist[col] == cv_nextmap.value)
+ {
+ lsrow = row;
+ lscol = col;
+ return true;
+ }
+ row++;
+ }
+ col++;
+ }
+ return true;
+}
+#endif
+
+static INT32 M_CountRowsToShowOnPlatter(INT32 gt)
+{
+ INT32 mapnum = 0, prevmapnum = 0, col = 0, rows = 0;
+
+ while (mapnum < NUMMAPS)
+ {
+ if (M_CanShowLevelOnPlatter(mapnum, gt))
+ {
+ if (rows == 0)
+ rows++;
+ else
+ {
+ if (col == 2
+ || (mapheaderinfo[prevmapnum]->menuflags & LF2_WIDEICON)
+ || (mapheaderinfo[mapnum]->menuflags & LF2_WIDEICON)
+ || !(fastcmp(mapheaderinfo[mapnum]->selectheading, mapheaderinfo[prevmapnum]->selectheading)))
+ {
+ col = 0;
+ rows++;
+ }
+ else
+ col++;
+ }
+ prevmapnum = mapnum;
+ }
+ mapnum++;
+ }
+
+ return rows;
+}
+
+//
+// M_PrepareLevelPlatter
+//
+// Prepares a tasty dish of zones and acts!
+// Call before any attempt to access a level platter.
+//
+static boolean M_PrepareLevelPlatter(INT32 gt)
+{
+ INT32 numrows = M_CountRowsToShowOnPlatter(gt);
+ INT32 mapnum = 0, prevmapnum = 0, col = 0, row = 0;
+
+ if (!numrows)
+ return false;
+
+ if (levelselect.rows)
+ Z_Free(levelselect.rows);
+ levelselect.rows = NULL;
+
+ levelselect.numrows = numrows;
+ levelselect.rows = Z_Realloc(levelselect.rows, numrows*sizeof(levelselectrow_t), PU_STATIC, NULL);
+ if (!levelselect.rows)
+ I_Error("Insufficient memory to prepare level platter");
+
+ // done here so lsrow and lscol can be set if cv_nextmap is on the platter
+ lsrow = lscol = lstic = lshli = lsoffs[0] = lsoffs[1] = 0;
+
+ while (mapnum < NUMMAPS)
+ {
+ if (M_CanShowLevelOnPlatter(mapnum, gt))
+ {
+ const INT32 actnum = mapheaderinfo[mapnum]->actnum;
+ const boolean headingisname = (fastcmp(mapheaderinfo[mapnum]->selectheading, mapheaderinfo[mapnum]->lvlttl));
+ const boolean wide = (mapheaderinfo[mapnum]->menuflags & LF2_WIDEICON);
+
+ // preparing next position to drop mapnum into
+ if (levelselect.rows[0].maplist[0])
+ {
+ if (col == 2 // no more space on the row?
+ || wide
+ || (mapheaderinfo[prevmapnum]->menuflags & LF2_WIDEICON)
+ || !(fastcmp(mapheaderinfo[mapnum]->selectheading, mapheaderinfo[prevmapnum]->selectheading))) // a new heading is starting?
+ {
+ col = 0;
+ row++;
+ }
+ else
+ col++;
+ }
+
+ levelselect.rows[row].maplist[col] = mapnum+1; // putting the map on the platter
+ levelselect.rows[row].mapavailable[col] = M_LevelAvailableOnPlatter(mapnum);
+
+ if ((lswide(row) = wide)) // intentionally assignment
+ {
+ levelselect.rows[row].maplist[2] = levelselect.rows[row].maplist[1] = levelselect.rows[row].maplist[0];
+ levelselect.rows[row].mapavailable[2] = levelselect.rows[row].mapavailable[1] = levelselect.rows[row].mapavailable[0];
+ }
+
+ if (cv_nextmap.value == mapnum+1) // A little quality of life improvement.
+ {
+ lsrow = row;
+ lscol = col;
+ }
+
+ // individual map name
+ if (levelselect.rows[row].mapavailable[col])
+ {
+ if (headingisname)
+ {
+ if (actnum)
+ sprintf(levelselect.rows[row].mapnames[col], "ACT %d", actnum);
+ else
+ sprintf(levelselect.rows[row].mapnames[col], "THE ACT");
+ }
+ else if (wide)
+ {
+ // Yes, with LF2_WIDEICON it'll continue on over into the next 17+1 char block. That's alright; col is always zero, the string is contiguous, and the maximum length is lvlttl[22] + ' ' + ZONE + ' ' + INT32, which is about 39 or so - barely crossing into the third column.
+ char* mapname = G_BuildMapTitle(mapnum+1);
+ strcpy(levelselect.rows[row].mapnames[col], (const char *)mapname);
+ Z_Free(mapname);
+ }
+ else
+ {
+ char mapname[22+1+11]; // lvlttl[22] + ' ' + INT32
+
+ if (actnum)
+ sprintf(mapname, "%s %d", mapheaderinfo[mapnum]->lvlttl, actnum);
+ else
+ sprintf(mapname, "%s", mapheaderinfo[mapnum]->lvlttl);
+
+ if (strlen(mapname) >= 17)
+ sprintf(mapname+17-3, "...");
+
+ strcpy(levelselect.rows[row].mapnames[col], (const char *)mapname);
+ }
+ }
+ else
+ sprintf(levelselect.rows[row].mapnames[col], "???");
+
+ // creating header text
+ if (!col && (!row || !(fastcmp(mapheaderinfo[mapnum]->selectheading, mapheaderinfo[levelselect.rows[row-1].maplist[0]-1]->selectheading))))
+ {
+ if (!levelselect.rows[row].mapavailable[col])
+ sprintf(levelselect.rows[row].header, "???");
+ else
+ {
+ sprintf(levelselect.rows[row].header, "%s", mapheaderinfo[mapnum]->selectheading);
+ if (!(mapheaderinfo[mapnum]->levelflags & LF_NOZONE) && headingisname)
+ {
+ sprintf(levelselect.rows[row].header + strlen(levelselect.rows[row].header), " ZONE");
+ }
+ }
+ }
+
+ prevmapnum = mapnum;
+ }
+
+ mapnum++;
+ }
+
+ if (levselp[0][0]) // never going to have some provided but not all, saves individually checking
+ {
+ W_UnlockCachedPatch(levselp[0][0]);
+ W_UnlockCachedPatch(levselp[0][1]);
+ W_UnlockCachedPatch(levselp[0][2]);
+
+ W_UnlockCachedPatch(levselp[1][0]);
+ W_UnlockCachedPatch(levselp[1][1]);
+ W_UnlockCachedPatch(levselp[1][2]);
+ }
+
+ levselp[0][0] = W_CachePatchName("SLCT1LVL", PU_STATIC);
+ levselp[0][1] = W_CachePatchName("SLCT2LVL", PU_STATIC);
+ levselp[0][2] = W_CachePatchName("BLANKLVL", PU_STATIC);
+
+ levselp[1][0] = W_CachePatchName("SLCT1LVW", PU_STATIC);
+ levselp[1][1] = W_CachePatchName("SLCT2LVW", PU_STATIC);
+ levselp[1][2] = W_CachePatchName("BLANKLVW", PU_STATIC);
+
+ return true;
+}
+
+#define selectvalnextmapnobrace(column) selectval = levelselect.rows[lsrow].maplist[column];\
+ if (selectval && levelselect.rows[lsrow].mapavailable[column])\
+ {\
+ CV_SetValue(&cv_nextmap, selectval);
+
+#define selectvalnextmap(column) selectvalnextmapnobrace(column)}
+
+//
+// M_HandleLevelPlatter
+//
+// Reacts to your key inputs. Basically a mini menu thinker.
+//
+static void M_HandleLevelPlatter(INT32 choice)
+{
+ boolean exitmenu = false; // exit to previous menu
+ INT32 selectval;
+
+ switch (choice)
+ {
+ case KEY_DOWNARROW:
+ lsrow++;
+ if (lsrow == levelselect.numrows)
+ lsrow = 0;
+
+ lsoffs[0] = lsvseperation(lsrow);
+
+ if (levelselect.rows[lsrow].header[0])
+ lshli = lsrow;
+ // no else needed - headerless lines associate upwards, so moving down to a row without a header is identity
+
+ S_StartSound(NULL,sfx_s3kb7);
+
+ selectvalnextmap(lscol) else selectvalnextmap(0)
+ break;
+
+ case KEY_UPARROW:
+ lsoffs[0] = -lsvseperation(lsrow);
+
+ lsrow--;
+ if (lsrow == UINT8_MAX)
+ lsrow = levelselect.numrows-1;
+
+ if (levelselect.rows[lsrow].header[0])
+ lshli = lsrow;
+ else
+ {
+ UINT8 iter = lsrow;
+ do
+ iter = ((iter == 0) ? levelselect.numrows-1 : iter-1);
+ while ((iter != lsrow) && !(levelselect.rows[iter].header[0]));
+ lshli = iter;
+ }
+
+ S_StartSound(NULL,sfx_s3kb7);
+
+ selectvalnextmap(lscol) else selectvalnextmap(0)
+ break;
+
+ case KEY_LEFTARROW:
+ if (lscol > 0)
+ {
+ lscol--;
+
+ lsoffs[1] = (lswide(lsrow) ? -8 : lshseperation);
+ S_StartSound(NULL,sfx_s3kb7);
+
+ selectvalnextmap(lscol) else selectvalnextmap(0)
+ }
+ else if (!lsoffs[1]) // prevent sound spam
+ {
+ lsoffs[1] = -8;
+ S_StartSound(NULL,sfx_s3kb7);
+ }
+ break;
+
+ case KEY_RIGHTARROW:
+ if (lscol < 2)
+ {
+ lscol++;
+
+ lsoffs[1] = (lswide(lsrow) ? 8 : -lshseperation);
+ S_StartSound(NULL,sfx_s3kb7);
+
+ selectvalnextmap(lscol) else selectvalnextmap(0)
+ }
+ else if (!lsoffs[1]) // prevent sound spam
+ {
+ lsoffs[1] = 8;
+ S_StartSound(NULL,sfx_s3kb7);
+ }
+ break;
+
+ case KEY_ENTER:
+ selectvalnextmapnobrace(lscol)
+
+ lsoffs[0] = lsoffs[1] = 0;
+ S_StartSound(NULL,sfx_menu1);
+ if (gamestate == GS_TIMEATTACK)
+ M_SetupNextMenu(currentMenu->prevMenu);
+ else if (currentMenu == &MISC_ChangeLevelDef)
+ {
+ if (currentMenu->prevMenu && currentMenu->prevMenu->prevMenu != &MPauseDef)
+ M_SetupNextMenu(currentMenu->prevMenu->prevMenu);
+ else
+ M_ChangeLevel(0);
+ Z_Free(levelselect.rows);
+ levelselect.rows = NULL;
+ }
+ else
+ M_LevelSelectWarp(0);
+ Nextmap_OnChange();
+ }
+ else if (!lsoffs[0]) // prevent sound spam
+ {
+ lsoffs[0] = -8;
+ S_StartSound(NULL,sfx_s3kb2);
+ }
+ break;
+
+ case KEY_ESCAPE:
+ exitmenu = true;
+ break;
+
+ default:
+ break;
+ }
+
+ if (exitmenu)
+ {
+ if (currentMenu->prevMenu)
+ {
+ M_SetupNextMenu(currentMenu->prevMenu);
+ Nextmap_OnChange();
+ }
+ else
+ M_ClearMenus(true);
+ Z_Free(levelselect.rows);
+ levelselect.rows = NULL;
+ }
+}
+
+void M_DrawLevelPlatterHeader(INT32 y, const char *header, boolean headerhighlight)
+{
+ y += lsheadingheight - 12;
+ V_DrawString(19, y, (headerhighlight ? V_YELLOWMAP : 0), header);
+ y += 9;
+ if ((y >= 0) && (y < 200))
+ {
+ V_DrawFill(19, y, 281, 1, (headerhighlight ? yellowmap[3] : 3));
+ V_DrawFill(300, y, 1, 1, 26);
+ }
+ y++;
+ if ((y >= 0) && (y < 200))
+ {
+ V_DrawFill(19, y, 282, 1, 26);
+ }
+}
+
+static void M_DrawLevelPlatterWideMap(UINT8 row, UINT8 col, INT32 x, INT32 y, boolean highlight)
+{
+ patch_t *patch;
+
+ INT32 map = levelselect.rows[row].maplist[col];
+ if (!map)
+ return;
+
+ // A 564x100 image of the level as entry MAPxxW
+ if (!(levelselect.rows[row].mapavailable[col]))
+ V_DrawSmallScaledPatch(x, y, V_STATIC, levselp[1][2]); // static - make secret maps look ENTICING
+ else
+ {
+ if (W_CheckNumForName(va("%sW", G_BuildMapName(map))) != LUMPERROR)
+ patch = W_CachePatchName(va("%sW", G_BuildMapName(map)), PU_CACHE);
+ else
+ patch = levselp[1][2]; // don't static to indicate that it's just a normal level
+
+ V_DrawSmallScaledPatch(x, y, 0, patch);
+ }
+
+ if ((y+50) < 200)
+ {
+ INT32 topy = (y+50), h = 8;
+
+ if (topy < 0)
+ {
+ h += topy;
+ topy = 0;
+ }
+ else if (topy + h >= 200)
+ h = 200 - y;
+ if (h > 0)
+ V_DrawFill(x, topy, 282, h,
+ ((mapheaderinfo[map-1]->unlockrequired < 0)
+ ? 159 : 63));
+ }
+
+ V_DrawString(x, y+50, (highlight ? V_YELLOWMAP : 0), levelselect.rows[row].mapnames[col]);
+}
+
+static void M_DrawLevelPlatterMap(UINT8 row, UINT8 col, INT32 x, INT32 y, boolean highlight)
+{
+ patch_t *patch;
+
+ INT32 map = levelselect.rows[row].maplist[col];
+ if (!map)
+ return;
+
+ // A 160x100 image of the level as entry MAPxxP
+ if (!(levelselect.rows[row].mapavailable[col]))
+ V_DrawSmallScaledPatch(x, y, V_STATIC, levselp[0][2]); // static - make secret maps look ENTICING
+ else
+ {
+ if (W_CheckNumForName(va("%sP", G_BuildMapName(map))) != LUMPERROR)
+ patch = W_CachePatchName(va("%sP", G_BuildMapName(map)), PU_CACHE);
+ else
+ patch = levselp[0][2]; // don't static to indicate that it's just a normal level
+
+ V_DrawSmallScaledPatch(x, y, 0, patch);
+ }
+
+ if ((y+50) < 200)
+ {
+ INT32 topy = (y+50), h = 8;
+
+ if (topy < 0)
+ {
+ h += topy;
+ topy = 0;
+ }
+ else if (topy + h >= 200)
+ h = 200 - y;
+ if (h > 0)
+ V_DrawFill(x, topy, 80, h,
+ ((mapheaderinfo[map-1]->unlockrequired < 0)
+ ? 159 : 63));
+ }
+
+ if (strlen(levelselect.rows[row].mapnames[col]) > 6) // "AERIAL GARDEN" vs "ACT 18" - "THE ACT" intentionally compressed
+ V_DrawThinString(x, y+50, (highlight ? V_YELLOWMAP : 0), levelselect.rows[row].mapnames[col]);
+ else
+ V_DrawString(x, y+50, (highlight ? V_YELLOWMAP : 0), levelselect.rows[row].mapnames[col]);
+}
+
+static void M_DrawLevelPlatterRow(UINT8 row, INT32 y)
+{
+ UINT8 col;
+ const boolean rowhighlight = (row == lsrow);
+ if (levelselect.rows[row].header[0])
+ {
+ M_DrawLevelPlatterHeader(y, levelselect.rows[row].header, (rowhighlight || (row == lshli)));
+ y += lsheadingheight;
+ }
+
+ if (lswide(row))
+ M_DrawLevelPlatterWideMap(row, 0, lsbasex, y, rowhighlight);
+ else
+ {
+ for (col = 0; col < 3; col++)
+ M_DrawLevelPlatterMap(row, col, lsbasex+(col*lshseperation), y, (rowhighlight && (col == lscol)));
+ }
+}
+
+static void M_DrawLevelPlatterMenu(void)
+{
+ UINT8 iter = lsrow, sizeselect = (lswide(lsrow) ? 1 : 0);
+ INT32 y = lsbasey + lsoffs[0] - getheadingoffset(lsrow);
+ const INT32 cursorx = (sizeselect ? 0 : (lscol*lshseperation));
+
+ if (++lstic == 32)
+ lstic = 0;
+
+ if (gamestate == GS_TIMEATTACK)
+ V_DrawPatchFill(W_CachePatchName("SRB2BACK", PU_CACHE));
+
+ // finds row at top of the screen
+ while (y > 0)
+ {
+ iter = ((iter == 0) ? levelselect.numrows-1 : iter-1);
+ y -= lsvseperation(iter);
+ }
+
+ // draw from top to bottom
+ while (y < 200)
+ {
+ M_DrawLevelPlatterRow(iter, y);
+ y += lsvseperation(iter);
+ iter = ((iter == levelselect.numrows-1) ? 0 : iter+1);
+ }
+
+ // draw cursor box
+ V_DrawSmallScaledPatch(lsbasex + cursorx + lsoffs[1], lsbasey, 0, ((lstic & 8) ? levselp[sizeselect][0] : levselp[sizeselect][1]));
+
+ if (levelselect.rows[lsrow].maplist[lscol])
+ V_DrawScaledPatch(lsbasex + cursorx-17, lsbasey+50+lsoffs[0], 0, W_CachePatchName("M_CURSOR", PU_CACHE));
+
+ // handle movement of cursor box
+ if (abs(lsoffs[0]) > 1)
+ lsoffs[0] = 2*lsoffs[0]/3;
+ else
+ lsoffs[0] = 0;
+
+ if (abs(lsoffs[1]) > 1)
+ lsoffs[1] = 2*lsoffs[1]/3;
+ else
+ lsoffs[1] = 0;
+
+ M_DrawMenuTitle();
+}
+
+//
+// M_CanShowLevelInList
+//
+// Determines whether to show a given map in level-select lists where you don't want to see locked levels.
+// Set gt = -1 to ignore gametype.
+//
+boolean M_CanShowLevelInList(INT32 mapnum, INT32 gt)
+{
+ return (M_CanShowLevelOnPlatter(mapnum, gt) && M_LevelAvailableOnPlatter(mapnum));
+}
+
+static INT32 M_GetFirstLevelInList(INT32 gt)
{
INT32 mapnum;
for (mapnum = 0; mapnum < NUMMAPS; mapnum++)
- if (M_CanShowLevelInList(mapnum, -1))
+ if (M_CanShowLevelInList(mapnum, gt))
return mapnum + 1;
return 1;
@@ -3831,7 +4623,7 @@ static void M_HandleImageDef(INT32 choice)
static void M_PandorasBox(INT32 choice)
{
(void)choice;
- CV_StealthSetValue(&cv_dummyrings, max(players[consoleplayer].health - 1, 0));
+ CV_StealthSetValue(&cv_dummyrings, max(players[consoleplayer].rings, 0));
CV_StealthSetValue(&cv_dummylives, players[consoleplayer].lives);
CV_StealthSetValue(&cv_dummycontinues, players[consoleplayer].continues);
M_SetupNextMenu(&SR_PandoraDef);
@@ -3839,7 +4631,7 @@ static void M_PandorasBox(INT32 choice)
static boolean M_ExitPandorasBox(void)
{
- if (cv_dummyrings.value != max(players[consoleplayer].health - 1, 0))
+ if (cv_dummyrings.value != max(players[consoleplayer].rings, 0))
COM_ImmedExecute(va("setrings %d", cv_dummyrings.value));
if (cv_dummylives.value != players[consoleplayer].lives)
COM_ImmedExecute(va("setlives %d", cv_dummylives.value));
@@ -3864,6 +4656,7 @@ static void M_ChangeLevel(INT32 choice)
static void M_ConfirmSpectate(INT32 choice)
{
(void)choice;
+ // We allow switching to spectator even if team changing is not allowed
M_ClearMenus(true);
COM_ImmedExecute("changeteam spectator");
}
@@ -3871,6 +4664,11 @@ static void M_ConfirmSpectate(INT32 choice)
static void M_ConfirmEnterGame(INT32 choice)
{
(void)choice;
+ if (!cv_allowteamchange.value)
+ {
+ M_StartMessage(M_GetText("The server is not allowing\nteam changes at this time.\nPress a key.\n"), NULL, MM_NOTHING);
+ return;
+ }
M_ClearMenus(true);
COM_ImmedExecute("changeteam playing");
}
@@ -3921,7 +4719,7 @@ static void M_Options(INT32 choice)
(void)choice;
// if the player is not admin or server, disable server options
- OP_MainMenu[5].status = (Playing() && !(server || adminplayer == consoleplayer)) ? (IT_GRAYEDOUT) : (IT_STRING|IT_SUBMENU);
+ OP_MainMenu[5].status = (Playing() && !(server || adminplayer == consoleplayer)) ? (IT_GRAYEDOUT) : (IT_STRING|IT_CALL);
// if the player is playing _at all_, disable the erase data options
OP_DataOptionsMenu[1].status = (Playing()) ? (IT_GRAYEDOUT) : (IT_STRING|IT_SUBMENU);
@@ -4023,27 +4821,323 @@ static void M_LevelSelectWarp(INT32 choice)
UINT8 skyRoomMenuTranslations[MAXUNLOCKABLES];
-#define NUMCHECKLIST 8
+static boolean checklist_cangodown; // uuuueeerggghhhh HACK
+
+static void M_HandleChecklist(INT32 choice)
+{
+ INT32 j;
+ switch (choice)
+ {
+ case KEY_DOWNARROW:
+ S_StartSound(NULL, sfx_menu1);
+ if (checklist_cangodown)
+ {
+ for (j = check_on+1; j < MAXUNLOCKABLES; j++)
+ {
+ if (!(unlockables[j].name[0] == 0 //|| unlockables[j].nochecklist
+ || !unlockables[j].conditionset || unlockables[j].conditionset > MAXCONDITIONSETS))
+ break;
+ }
+ if (j != MAXUNLOCKABLES)
+ check_on = j;
+ }
+ return;
+
+ case KEY_UPARROW:
+ S_StartSound(NULL, sfx_menu1);
+ for (j = check_on-1; j > -1; j--)
+ {
+ if (!(unlockables[j].name[0] == 0 //|| unlockables[j].nochecklist
+ || !unlockables[j].conditionset || unlockables[j].conditionset > MAXCONDITIONSETS))
+ break;
+ }
+ if (j != -1)
+ check_on = j;
+ return;
+
+ case KEY_ESCAPE:
+ if (currentMenu->prevMenu)
+ M_SetupNextMenu(currentMenu->prevMenu);
+ else
+ M_ClearMenus(true);
+ return;
+ default:
+ break;
+ }
+}
+
+#define addy(add) { y += add; if ((y - currentMenu->y) > (scrollareaheight*2)) goto finishchecklist; }
+
static void M_DrawChecklist(void)
{
- INT32 i, j = 0;
+ INT32 i = check_on, j = 0, y = currentMenu->y;
+ UINT32 condnum, previd, maxcond;
+ condition_t *cond;
- for (i = 0; i < MAXUNLOCKABLES; i++)
+ // draw title (or big pic)
+ M_DrawMenuTitle();
+
+ if (check_on)
+ V_DrawString(10, y, V_YELLOWMAP, "\x1A");
+
+ while (i < MAXUNLOCKABLES)
{
- if (unlockables[i].name[0] == 0 || unlockables[i].nochecklist
+ if (unlockables[i].name[0] == 0 //|| unlockables[i].nochecklist
|| !unlockables[i].conditionset || unlockables[i].conditionset > MAXCONDITIONSETS)
continue;
- V_DrawString(8, 8+(24*j), V_RETURN8, unlockables[i].name);
- V_DrawString(160, 8+(24*j), V_RETURN8, V_WordWrap(160, 292, 0, unlockables[i].objective));
+ V_DrawString(currentMenu->x, y, ((unlockables[i].unlocked) ? V_GREENMAP : V_TRANSLUCENT), ((unlockables[i].unlocked || !unlockables[i].nochecklist) ? unlockables[i].name : M_CreateSecretMenuOption(unlockables[i].name)));
+
+ for (j = i+1; j < MAXUNLOCKABLES; j++)
+ {
+ if (!(unlockables[j].name[0] == 0 //|| unlockables[j].nochecklist
+ || !unlockables[j].conditionset || unlockables[j].conditionset > MAXCONDITIONSETS))
+ break;
+ }
+ if ((j != MAXUNLOCKABLES) && (unlockables[i].conditionset == unlockables[j].conditionset))
+ addy(8)
+ else
+ {
+ if ((maxcond = conditionSets[unlockables[i].conditionset-1].numconditions))
+ {
+ cond = conditionSets[unlockables[i].conditionset-1].condition;
+ previd = cond[0].id;
+ addy(2);
+
+ if (unlockables[i].objective[0] != '/')
+ {
+ addy(8);
+ V_DrawString(currentMenu->x, y,
+ V_ALLOWLOWERCASE,
+ va("\x1E %s", unlockables[i].objective));
+ }
+ else
+ {
+ for (condnum = 0; condnum < maxcond; condnum++)
+ {
+ const char *beat = "!";
+
+ if (cond[condnum].id != previd)
+ {
+ addy(8);
+ V_DrawString(currentMenu->x + 4, y, V_YELLOWMAP, "OR");
+ }
+
+ addy(8);
+
+ switch (cond[condnum].type)
+ {
+ case UC_PLAYTIME:
+ {
+ UINT32 hours = G_TicsToHours(cond[condnum].requirement);
+ UINT32 minutes = G_TicsToMinutes(cond[condnum].requirement, false);
+ UINT32 seconds = G_TicsToSeconds(cond[condnum].requirement);
+
+#define getplural(field) ((field == 1) ? "" : "s")
+ if (hours)
+ {
+ if (minutes)
+ beat = va("Play the game for %d hour%s %d minute%s", hours, getplural(hours), minutes, getplural(minutes));
+ else
+ beat = va("Play the game for %d hour%s", hours, getplural(hours));
+ }
+ else
+ {
+ if (minutes && seconds)
+ beat = va("Play the game for %d minute%s %d second%s", minutes, getplural(minutes), seconds, getplural(seconds));
+ else if (minutes)
+ beat = va("Play the game for %d minute%s", minutes, getplural(minutes));
+ else
+ beat = va("Play the game for %d second%s", seconds, getplural(seconds));
+ }
+#undef getplural
+ }
+ break;
+ case UC_MAPVISITED:
+ case UC_MAPBEATEN:
+ case UC_MAPALLEMERALDS:
+ case UC_MAPULTIMATE:
+ case UC_MAPPERFECT:
+ {
+ char *title = G_BuildMapTitle(cond[condnum].requirement);
+
+ if (title)
+ {
+ const char *level = ((M_MapLocked(cond[condnum].requirement) || !((mapheaderinfo[cond[condnum].requirement-1]->menuflags & LF2_NOVISITNEEDED) || mapvisited[cond[condnum].requirement-1])) ? M_CreateSecretMenuOption(title) : title);
+
+ switch (cond[condnum].type)
+ {
+ case UC_MAPVISITED:
+ beat = va("Visit %s", level);
+ break;
+ case UC_MAPALLEMERALDS:
+ beat = va("Beat %s with all emeralds", level);
+ break;
+ case UC_MAPULTIMATE:
+ beat = va("Beat %s in Ultimate mode", level);
+ break;
+ case UC_MAPPERFECT:
+ beat = va("Get all rings in %s", level);
+ break;
+ case UC_MAPBEATEN:
+ default:
+ beat = va("Beat %s", level);
+ break;
+ }
+ Z_Free(title);
+ }
+ }
+ break;
+ case UC_MAPSCORE:
+ case UC_MAPTIME:
+ case UC_MAPRINGS:
+ {
+ char *title = G_BuildMapTitle(cond[condnum].extrainfo1);
+
+ if (title)
+ {
+ const char *level = ((M_MapLocked(cond[condnum].extrainfo1) || !((mapheaderinfo[cond[condnum].extrainfo1-1]->menuflags & LF2_NOVISITNEEDED) || mapvisited[cond[condnum].extrainfo1-1])) ? M_CreateSecretMenuOption(title) : title);
+
+ switch (cond[condnum].type)
+ {
+ case UC_MAPSCORE:
+ beat = va("Get %d points in %s", cond[condnum].requirement, level);
+ break;
+ case UC_MAPTIME:
+ beat = va("Beat %s in %d:%d.%d", level,
+ G_TicsToMinutes(cond[condnum].requirement, true),
+ G_TicsToSeconds(cond[condnum].requirement),
+ G_TicsToCentiseconds(cond[condnum].requirement));
+ break;
+ case UC_MAPRINGS:
+ beat = va("Get %d rings in %s", cond[condnum].requirement, level);
+ break;
+ default:
+ break;
+ }
+ Z_Free(title);
+ }
+ }
+ break;
+ case UC_OVERALLSCORE:
+ case UC_OVERALLTIME:
+ case UC_OVERALLRINGS:
+ {
+ switch (cond[condnum].type)
+ {
+ case UC_OVERALLSCORE:
+ beat = va("Get %d points over all maps", cond[condnum].requirement);
+ break;
+ case UC_OVERALLTIME:
+ beat = va("Get a total time of less than %d:%d.%d",
+ G_TicsToMinutes(cond[condnum].requirement, true),
+ G_TicsToSeconds(cond[condnum].requirement),
+ G_TicsToCentiseconds(cond[condnum].requirement));
+ break;
+ case UC_OVERALLRINGS:
+ beat = va("Get %d rings over all maps", cond[condnum].requirement);
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+ case UC_GAMECLEAR:
+ case UC_ALLEMERALDS:
+ {
+ const char *emeraldtext = ((cond[condnum].type == UC_ALLEMERALDS) ? " with all emeralds" : "");
+ if (cond[condnum].requirement != 1)
+ beat = va("Beat the game %d times%s",
+ cond[condnum].requirement, emeraldtext);
+ else
+ beat = va("Beat the game%s",
+ emeraldtext);
+ }
+ break;
+ case UC_TOTALEMBLEMS:
+ beat = va("Collect %s%d emblems", ((numemblems+numextraemblems == cond[condnum].requirement) ? "all " : ""), cond[condnum].requirement);
+ break;
+ case UC_NIGHTSTIME:
+ case UC_NIGHTSSCORE:
+ case UC_NIGHTSGRADE:
+ {
+ char *title = G_BuildMapTitle(cond[condnum].extrainfo1);
+
+ if (title)
+ {
+ const char *level = ((M_MapLocked(cond[condnum].extrainfo1) || !((mapheaderinfo[cond[condnum].extrainfo1-1]->menuflags & LF2_NOVISITNEEDED) || mapvisited[cond[condnum].extrainfo1-1])) ? M_CreateSecretMenuOption(title) : title);
+
+ switch (cond[condnum].type)
+ {
+ case UC_NIGHTSSCORE:
+ if (cond[condnum].extrainfo2)
+ beat = va("Get %d points in %s, mare %d", cond[condnum].requirement, level, cond[condnum].extrainfo2);
+ else
+ beat = va("Get %d points in %s", cond[condnum].requirement, level);
+ break;
+ case UC_NIGHTSTIME:
+ if (cond[condnum].extrainfo2)
+ beat = va("Beat %s, mare %d in %d:%d.%d", level, cond[condnum].extrainfo2,
+ G_TicsToMinutes(cond[condnum].requirement, true),
+ G_TicsToSeconds(cond[condnum].requirement),
+ G_TicsToCentiseconds(cond[condnum].requirement));
+ else
+ beat = va("Beat %s in %d:%d.%d",
+ level,
+ G_TicsToMinutes(cond[condnum].requirement, true),
+ G_TicsToSeconds(cond[condnum].requirement),
+ G_TicsToCentiseconds(cond[condnum].requirement));
+ break;
+ case UC_NIGHTSGRADE:
+ {
+ char grade = ('F' - (char)cond[condnum].requirement);
+ if (grade < 'A')
+ grade = 'A';
+ if (cond[condnum].extrainfo2)
+ beat = va("Get grade %c in %s, mare %d", grade, level, cond[condnum].extrainfo2);
+ else
+ beat = va("Get grade %c in %s", grade, level);
+ }
+ break;
+ default:
+ break;
+ }
+ Z_Free(title);
+ }
+ }
+ break;
+ case UC_TRIGGER:
+ case UC_EMBLEM:
+ case UC_CONDITIONSET:
+ default:
+ y -= 8; // Nope, not showing this.
+ break;
+ }
+ if (beat[0] != '!')
+ {
+ V_DrawString(currentMenu->x, y, 0, "\x1E");
+ V_DrawString(currentMenu->x+12, y, V_ALLOWLOWERCASE, beat);
+ }
+ previd = cond[condnum].id;
+ }
+ }
+ }
+ addy(12);
+ }
+ i = j;
+
+ /*V_DrawString(160, 8+(24*j), V_RETURN8, V_WordWrap(160, 292, 0, unlockables[i].objective));
if (unlockables[i].unlocked)
V_DrawString(308, 8+(24*j), V_YELLOWMAP, "Y");
else
- V_DrawString(308, 8+(24*j), V_YELLOWMAP, "N");
+ V_DrawString(308, 8+(24*j), V_YELLOWMAP, "N");*/
+ }
- if (++j >= NUMCHECKLIST)
- break;
+finishchecklist:
+ if ((checklist_cangodown = ((y - currentMenu->y) > (scrollareaheight*2)))) // haaaaaaacks.
+ {
+ V_DrawString(10, currentMenu->y+(scrollareaheight*2), V_YELLOWMAP, "\x1B");
}
}
@@ -4097,27 +5191,6 @@ static void M_DrawEmblemHints(void)
M_DrawGenericMenu();
}
-static void M_DrawLevelSelectMenu(void)
-{
- M_DrawGenericMenu();
-
- if (cv_nextmap.value)
- {
- lumpnum_t lumpnum;
- patch_t *PictureOfLevel;
-
- // A 160x100 image of the level as entry MAPxxP
- lumpnum = W_CheckNumForName(va("%sP", G_BuildMapName(cv_nextmap.value)));
-
- if (lumpnum != LUMPERROR)
- PictureOfLevel = W_CachePatchName(va("%sP", G_BuildMapName(cv_nextmap.value)), PU_CACHE);
- else
- PictureOfLevel = W_CachePatchName("BLANKLVL", PU_CACHE);
-
- V_DrawSmallScaledPatch(200, 110, 0, PictureOfLevel);
- }
-}
-
static void M_DrawSkyRoom(void)
{
INT32 i, y = 0;
@@ -4300,13 +5373,13 @@ static void M_CustomLevelSelect(INT32 choice)
SR_LevelSelectDef.prevMenu = currentMenu;
levellistmode = LLM_LEVELSELECT;
maplistoption = (UINT8)(unlockables[ul].variable);
- if (M_CountLevelsToShowInList() == 0)
+
+ if (!M_PrepareLevelPlatter(-1))
{
M_StartMessage(M_GetText("No selectable levels found.\n"),NULL,MM_NOTHING);
return;
}
- M_PrepareLevelSelect();
M_SetupNextMenu(&SR_LevelSelectDef);
}
@@ -4328,17 +5401,17 @@ static void M_SinglePlayerMenu(INT32 choice)
static void M_LoadGameLevelSelect(INT32 choice)
{
(void)choice;
+
+ SP_LevelSelectDef.prevMenu = currentMenu;
levellistmode = LLM_LEVELSELECT;
maplistoption = 1;
- if (M_CountLevelsToShowInList() == 0)
+
+ if (!M_PrepareLevelPlatter(-1))
{
M_StartMessage(M_GetText("No selectable levels found.\n"),NULL,MM_NOTHING);
return;
}
- SP_LevelSelectDef.prevMenu = currentMenu;
-
- M_PrepareLevelSelect();
M_SetupNextMenu(&SP_LevelSelectDef);
}
@@ -4780,19 +5853,19 @@ static void M_SetupChoosePlayer(INT32 choice)
UINT8 i;
UINT8 firstvalid = 255;
UINT8 lastvalid = 0;
+ boolean allowed = false;
char *name;
(void)choice;
- if (PlayerMenu[0].status & (IT_DYBIGSPACE)) // Correcting a hack that may be made below.
- PlayerMenu[0].status = (IT_DISABLED|(PlayerMenu[0].status & IT_CENTER));
+ SP_PlayerMenu[0].status &= ~IT_DYBIGSPACE; // Correcting a hack that may be made below.
for (i = 0; i < 32; i++) // Handle charsels, availability, and unlocks.
{
- if (PlayerMenu[i].status != IT_DISABLED) // If the character's disabled through SOC, there's nothing we can do for it.
+ if (description[i].used) // If the character's disabled through SOC, there's nothing we can do for it.
{
name = strtok(Z_StrDup(description[i].skinname), "&");
skinnum = R_SkinAvailable(name);
- if ((skinnum != -1) && (R_SkinUnlock(skinnum)))
+ if ((skinnum != -1) && (R_SkinUsable(-1, skinnum)))
{
// Handling order.
if (firstvalid == 255)
@@ -4804,14 +5877,13 @@ static void M_SetupChoosePlayer(INT32 choice)
}
lastvalid = i;
- // Handling visibility.
- if (PlayerMenu[i].status & (IT_DISABLED|IT_CENTER))
- PlayerMenu[i].status = IT_CALL;
+ if (i == char_on)
+ allowed = true;
+
if (description[i].picname[0] == '\0')
strncpy(description[i].picname, skins[skinnum].charsel, 8);
}
- else // Technically, character select icons without corresponding skins get bundled away behind this too. Sucks to be them.
- PlayerMenu[i].status = (IT_DISABLED|IT_CENTER);
+ // else -- Technically, character select icons without corresponding skins get bundled away behind this too. Sucks to be them.
Z_Free(name);
}
}
@@ -4827,7 +5899,7 @@ static void M_SetupChoosePlayer(INT32 choice)
}
else // We're being forced into a specific character, so might as well.
{
- PlayerMenu[0].status = (IT_CALL|IT_DYBIGSPACE|(PlayerMenu[0].status & IT_CENTER)); // This is a hack to make a non-IT_CALL character in slot 0 not softlock the game. IT_DYBIGSPACE is a dummy flag, whilst IT_CENTER is preserved.
+ SP_PlayerMenu[0].status |= IT_DYBIGSPACE; // This is a dummy flag hack to make a non-IT_CALL character in slot 0 not softlock the game.
M_ChoosePlayer(0);
return;
}
@@ -4836,14 +5908,82 @@ static void M_SetupChoosePlayer(INT32 choice)
if (Playing() == false)
{
S_StopMusic();
- S_ChangeMusicInternal("chrsel", true);
+ S_ChangeMusicInternal("_chsel", true);
}
SP_PlayerDef.prevMenu = currentMenu;
M_SetupNextMenu(&SP_PlayerDef);
- char_scroll = itemOn*128*FRACUNIT; // finish scrolling the menu
+ if (!allowed)
+ char_on = firstvalid;
+ char_scroll = 0; // finish scrolling the menu
Z_Free(char_notes);
- char_notes = NULL;
+ char_notes = V_WordWrap(0, 21*8, V_ALLOWLOWERCASE, description[char_on].notes);
+}
+
+//
+// M_HandleChoosePlayerMenu
+//
+// Reacts to your key inputs. Basically a mini menu thinker.
+//
+static void M_HandleChoosePlayerMenu(INT32 choice)
+{
+ boolean exitmenu = false; // exit to previous menu
+ INT32 selectval;
+
+ switch (choice)
+ {
+ case KEY_DOWNARROW:
+ if ((selectval = description[char_on].next) != char_on)
+ {
+ S_StartSound(NULL,sfx_s3kb7);
+ char_on = selectval;
+ char_scroll = -128*FRACUNIT;
+ Z_Free(char_notes);
+ char_notes = V_WordWrap(0, 21*8, V_ALLOWLOWERCASE, description[char_on].notes);
+ }
+ else if (!char_scroll)
+ {
+ S_StartSound(NULL,sfx_s3kb7);
+ char_scroll = 16*FRACUNIT;
+ }
+ break;
+
+ case KEY_UPARROW:
+ if ((selectval = description[char_on].prev) != char_on)
+ {
+ S_StartSound(NULL,sfx_s3kb7);
+ char_on = selectval;
+ char_scroll = 128*FRACUNIT;
+ Z_Free(char_notes);
+ char_notes = V_WordWrap(0, 21*8, V_ALLOWLOWERCASE, description[char_on].notes);
+ }
+ else if (!char_scroll)
+ {
+ S_StartSound(NULL,sfx_s3kb7);
+ char_scroll = -16*FRACUNIT;
+ }
+ break;
+
+ case KEY_ENTER:
+ S_StartSound(NULL, sfx_menu1);
+ M_ChoosePlayer(char_on);
+ break;
+
+ case KEY_ESCAPE:
+ exitmenu = true;
+ break;
+
+ default:
+ break;
+ }
+
+ if (exitmenu)
+ {
+ if (currentMenu->prevMenu)
+ M_SetupNextMenu(currentMenu->prevMenu);
+ else
+ M_ClearMenus(true);
+ }
}
// Draw the choose player setup menu, had some fun with player anim
@@ -4853,7 +5993,6 @@ static void M_DrawSetupChoosePlayerMenu(void)
patch_t *patch;
INT32 i, o;
UINT8 prev, next;
- boolean loophack = false;
// Black BG
V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
@@ -4862,37 +6001,20 @@ static void M_DrawSetupChoosePlayerMenu(void)
// Character select profile images!1
M_DrawTextBox(0, my, 16, 20);
- i = (itemOn*128 - (char_scroll / FRACUNIT));
-
- if (!char_notes)
- {
- if (i) // turns out this and the preceding check is better then (abs(i) > 128)
- {
- o = (lastdirection) ? -1 : 1;
- char_scroll = (itemOn + o)*128*FRACUNIT;
- i = -o*128;
- }
- char_notes = V_WordWrap(0, 21*8, V_ALLOWLOWERCASE, description[itemOn].notes);
- }
-
- if (abs(i) > 1)
- char_scroll += i*FRACUNIT>>2;
+ if (abs(char_scroll) > FRACUNIT)
+ char_scroll -= (char_scroll>>2);
else // close enough.
- char_scroll = itemOn*128*FRACUNIT; // just be exact now.
+ char_scroll = 0; // just be exact now.
- o = ((char_scroll / FRACUNIT) + 16);
+ o = (char_scroll >> FRACBITS) + 16;
- if (o < 0) // This hack is to prevent visual glitches when looping from the last character to the 1st character.
- loophack = true;
-
- if (loophack)
+ if (o < 0) // A little hacky...
+ {
+ i = description[char_on].prev;
o += 128;
-
- i = (o / 128);
- o = (o % 128);
-
- if (loophack)
- i = description[i].prev;
+ }
+ else
+ i = char_on;
// Get prev character...
prev = description[i].prev;
@@ -4903,7 +6025,7 @@ static void M_DrawSetupChoosePlayerMenu(void)
next = description[i].next;
// Draw prev character if it's visible and its number isn't greater than the current one or there's more than two
- if (o < 32) // (prev != i) was previously a part of this, but we don't need to check again after above.
+ if (o < 32)
{
patch = W_CachePatchName(description[prev].picname, PU_CACHE);
if (SHORT(patch->width) >= 256)
@@ -4923,31 +6045,24 @@ static void M_DrawSetupChoosePlayerMenu(void)
V_DrawCroppedPatch(8<width), o);
W_UnlockCachedPatch(patch);
}
-
- // current character
- if (PlayerMenu[i].status & IT_DISABLED) // Prevent flickering.
- i = (lastdirection) ? prev : next; // This actually causes duplication at slow scroll speeds (<16FU per tic), but thankfully we always go quickly.
}
- if (!(PlayerMenu[i].status & IT_DISABLED))
+ patch = W_CachePatchName(description[i].picname, PU_CACHE);
+ if (o >= 0 && o <= 32)
{
- patch = W_CachePatchName(description[i].picname, PU_CACHE);
- if (o >= 0 && o <= 32)
- {
- if (SHORT(patch->width) >= 256)
- V_DrawSmallScaledPatch(8, my + 40 - o, 0, patch);
- else
- V_DrawScaledPatch(8, my + 40 - o, 0, patch);
- }
+ if (SHORT(patch->width) >= 256)
+ V_DrawSmallScaledPatch(8, my + 40 - o, 0, patch);
else
- {
- if (SHORT(patch->width) >= 256)
- V_DrawCroppedPatch(8<width), SHORT(patch->height));
- else
- V_DrawCroppedPatch(8<width), SHORT(patch->height));
- }
- W_UnlockCachedPatch(patch);
+ V_DrawScaledPatch(8, my + 40 - o, 0, patch);
}
+ else
+ {
+ if (SHORT(patch->width) >= 256)
+ V_DrawCroppedPatch(8<width), SHORT(patch->height));
+ else
+ V_DrawCroppedPatch(8<width), SHORT(patch->height));
+ }
+ W_UnlockCachedPatch(patch);
// draw title (or big pic)
M_DrawMenuTitle();
@@ -4965,10 +6080,10 @@ static void M_ChoosePlayer(INT32 choice)
boolean ultmode = (ultimate_selectable && SP_PlayerDef.prevMenu == &SP_LoadDef && saveSlotSelected == NOSAVESLOT);
// skip this if forcecharacter or no characters available
- if (!(PlayerMenu[choice].status & IT_DYBIGSPACE))
+ if (!(SP_PlayerMenu[0].status & IT_DYBIGSPACE))
{
// M_SetupChoosePlayer didn't call us directly, that means we've been properly set up.
- char_scroll = itemOn*128*FRACUNIT; // finish scrolling the menu
+ char_scroll = 0; // finish scrolling the menu
M_DrawSetupChoosePlayerMenu(); // draw the finally selected character one last time for the fadeout
}
M_ClearMenus(true);
@@ -5001,6 +6116,10 @@ static void M_ChoosePlayer(INT32 choice)
G_DeferedInitNew(ultmode, G_BuildMapName(startmap), (UINT8)skinnum, false, fromlevelselect);
COM_BufAddText("dummyconsvar 1\n"); // G_DeferedInitNew doesn't do this
+
+ if (levelselect.rows)
+ Z_Free(levelselect.rows);
+ levelselect.rows = NULL;
}
// ===============
@@ -5033,23 +6152,24 @@ static void M_Statistics(INT32 choice)
statsMapList[j++] = i;
}
statsMapList[j] = -1;
- statsMax = j - 13 + numextraemblems;
+ statsMax = j - 11 + numextraemblems;
statsLocation = 0;
if (statsMax < 0)
statsMax = 0;
- M_SetupNextMenu(&SP_GameStatsDef);
+ M_SetupNextMenu(&SP_LevelStatsDef);
}
static void M_DrawStatsMaps(int location)
{
- INT32 y = 76, i = -1;
+ INT32 y = 80, i = -1;
INT16 mnum;
extraemblem_t *exemblem;
+ boolean dotopname = true, dobottomarrow = (location < statsMax);
- V_DrawString(20, y-12, 0, "LEVEL NAME");
- V_DrawString(248, y-12, 0, "EMBLEMS");
+ if (location)
+ V_DrawString(10, y, V_YELLOWMAP, "\x1A");
while (statsMapList[++i] != -1)
{
@@ -5058,6 +6178,13 @@ static void M_DrawStatsMaps(int location)
--location;
continue;
}
+ else if (dotopname)
+ {
+ V_DrawString(20, y, V_GREENMAP, "LEVEL NAME");
+ V_DrawString(248, y, V_GREENMAP, "EMBLEMS");
+ y += 8;
+ dotopname = false;
+ }
mnum = statsMapList[i];
M_DrawMapEmblems(mnum+1, 292, y);
@@ -5070,21 +6197,36 @@ static void M_DrawStatsMaps(int location)
y += 8;
if (y >= BASEVIDHEIGHT-8)
- return;
+ goto bottomarrow;
}
+ if (dotopname && !location)
+ {
+ V_DrawString(20, y, V_GREENMAP, "LEVEL NAME");
+ V_DrawString(248, y, V_GREENMAP, "EMBLEMS");
+ y += 8;
+ }
+ else if (location)
+ --location;
// Extra Emblems
for (i = -2; i < numextraemblems; ++i)
{
+ if (i == -1)
+ {
+ V_DrawString(20, y, V_GREENMAP, "EXTRA EMBLEMS");
+ if (location)
+ {
+ y += 8;
+ location++;
+ }
+ }
if (location)
{
--location;
continue;
}
- if (i == -1)
- V_DrawString(20, y, V_GREENMAP, "EXTRA EMBLEMS");
- else if (i >= 0)
+ if (i >= 0)
{
exemblem = &extraemblems[i];
@@ -5100,17 +6242,87 @@ static void M_DrawStatsMaps(int location)
y += 8;
if (y >= BASEVIDHEIGHT-8)
- return;
+ goto bottomarrow;
}
+bottomarrow:
+ if (dobottomarrow)
+ V_DrawString(10, y-8, V_YELLOWMAP, "\x1B");
}
static void M_DrawLevelStats(void)
{
- M_DrawMenuTitle();
- V_DrawCenteredString(BASEVIDWIDTH/2, 24, V_YELLOWMAP, "PAGE 2 OF 2");
+ char beststr[40];
- V_DrawString(72, 48, 0, va("x %d/%d", M_CountEmblems(), numemblems+numextraemblems));
- V_DrawScaledPatch(40, 48-4, 0, W_CachePatchName("EMBLICON", PU_STATIC));
+ tic_t besttime = 0;
+ UINT32 bestscore = 0;
+ UINT32 bestrings = 0;
+
+ INT32 i;
+ INT32 mapsunfinished = 0;
+ boolean bestunfinished[3] = {false, false, false};
+
+ M_DrawMenuTitle();
+
+ V_DrawString(20, 24, V_YELLOWMAP, "Total Play Time:");
+ V_DrawCenteredString(BASEVIDWIDTH/2, 32, 0, va("%i hours, %i minutes, %i seconds",
+ G_TicsToHours(totalplaytime),
+ G_TicsToMinutes(totalplaytime, false),
+ G_TicsToSeconds(totalplaytime)));
+
+ for (i = 0; i < NUMMAPS; i++)
+ {
+ boolean mapunfinished = false;
+
+ if (!mapheaderinfo[i] || !(mapheaderinfo[i]->menuflags & LF2_RECORDATTACK))
+ continue;
+
+ if (!mainrecords[i])
+ {
+ mapsunfinished++;
+ bestunfinished[0] = bestunfinished[1] = bestunfinished[2] = true;
+ continue;
+ }
+
+ if (mainrecords[i]->score > 0)
+ bestscore += mainrecords[i]->score;
+ else
+ mapunfinished = bestunfinished[0] = true;
+
+ if (mainrecords[i]->time > 0)
+ besttime += mainrecords[i]->time;
+ else
+ mapunfinished = bestunfinished[1] = true;
+
+ if (mainrecords[i]->rings > 0)
+ bestrings += mainrecords[i]->rings;
+ else
+ mapunfinished = bestunfinished[2] = true;
+
+ if (mapunfinished)
+ mapsunfinished++;
+ }
+
+ V_DrawString(20, 48, 0, "Combined records:");
+
+ if (mapsunfinished)
+ V_DrawString(20, 56, V_REDMAP, va("(%d unfinished)", mapsunfinished));
+ else
+ V_DrawString(20, 56, V_GREENMAP, "(complete)");
+
+ V_DrawString(36, 64, 0, va("x %d/%d", M_CountEmblems(), numemblems+numextraemblems));
+ V_DrawSmallScaledPatch(20, 64, 0, W_CachePatchName("EMBLICON", PU_STATIC));
+
+ sprintf(beststr, "%u", bestscore);
+ V_DrawString(BASEVIDWIDTH/2, 48, V_YELLOWMAP, "SCORE:");
+ V_DrawRightAlignedString(BASEVIDWIDTH-16, 48, (bestunfinished[0] ? V_REDMAP : 0), beststr);
+
+ sprintf(beststr, "%i:%02i:%02i.%02i", G_TicsToHours(besttime), G_TicsToMinutes(besttime, false), G_TicsToSeconds(besttime), G_TicsToCentiseconds(besttime));
+ V_DrawString(BASEVIDWIDTH/2, 56, V_YELLOWMAP, "TIME:");
+ V_DrawRightAlignedString(BASEVIDWIDTH-16, 56, (bestunfinished[1] ? V_REDMAP : 0), beststr);
+
+ sprintf(beststr, "%u", bestrings);
+ V_DrawString(BASEVIDWIDTH/2, 64, V_YELLOWMAP, "RINGS:");
+ V_DrawRightAlignedString(BASEVIDWIDTH-16, 64, (bestunfinished[2] ? V_REDMAP : 0), beststr);
M_DrawStatsMaps(statsLocation);
}
@@ -5134,120 +6346,19 @@ static void M_HandleLevelStats(INT32 choice)
--statsLocation;
break;
- case KEY_RIGHTARROW:
+ case KEY_PGDN:
S_StartSound(NULL, sfx_menu1);
- statsLocation += (statsLocation+15 >= statsMax) ? statsMax-statsLocation : 15;
+ statsLocation += (statsLocation+13 >= statsMax) ? statsMax-statsLocation : 13;
break;
- case KEY_LEFTARROW:
+ case KEY_PGUP:
S_StartSound(NULL, sfx_menu1);
- statsLocation -= (statsLocation < 15) ? statsLocation : 15;
+ statsLocation -= (statsLocation < 13) ? statsLocation : 13;
break;
case KEY_ESCAPE:
exitmenu = true;
break;
-
- case KEY_ENTER:
- S_StartSound(NULL, sfx_menu1);
- M_SetupNextMenu(&SP_GameStatsDef);
- break;
- }
- if (exitmenu)
- {
- if (currentMenu->prevMenu)
- M_SetupNextMenu(currentMenu->prevMenu);
- else
- M_ClearMenus(true);
- }
-}
-
-// Handle GAME statistics.
-static void M_DrawGameStats(void)
-{
- char beststr[40];
-
- tic_t besttime = 0;
- UINT32 bestscore = 0;
- UINT32 bestrings = 0;
-
- INT32 i;
- INT32 mapsunfinished[3] = {0, 0, 0};
-
- M_DrawMenuTitle();
- V_DrawCenteredString(BASEVIDWIDTH/2, 24, V_YELLOWMAP, "PAGE 1 OF 2");
-
- V_DrawString(32, 60, V_YELLOWMAP, "Total Play Time:");
- V_DrawRightAlignedString(BASEVIDWIDTH-32, 70, 0, va("%i hours, %i minutes, %i seconds",
- G_TicsToHours(totalplaytime),
- G_TicsToMinutes(totalplaytime, false),
- G_TicsToSeconds(totalplaytime)));
-
- for (i = 0; i < NUMMAPS; i++)
- {
- if (!mapheaderinfo[i] || !(mapheaderinfo[i]->menuflags & LF2_RECORDATTACK))
- continue;
-
- if (!mainrecords[i])
- {
- mapsunfinished[0]++;
- mapsunfinished[1]++;
- mapsunfinished[2]++;
- continue;
- }
-
- if (mainrecords[i]->score > 0)
- bestscore += mainrecords[i]->score;
- else
- mapsunfinished[0]++;
-
- if (mainrecords[i]->time > 0)
- besttime += mainrecords[i]->time;
- else
- mapsunfinished[1]++;
-
- if (mainrecords[i]->rings > 0)
- bestrings += mainrecords[i]->rings;
- else
- mapsunfinished[2]++;
-
- }
-
- V_DrawCenteredString(BASEVIDWIDTH/2, 90, 0, "* COMBINED RECORDS *");
-
- sprintf(beststr, "%u", bestscore);
- V_DrawString(32, 100, V_YELLOWMAP, "SCORE:");
- V_DrawRightAlignedString(BASEVIDWIDTH-32, 100, 0, beststr);
- if (mapsunfinished[0])
- V_DrawRightAlignedString(BASEVIDWIDTH-32, 108, V_REDMAP, va("(%d unfinished)", mapsunfinished[0]));
-
- sprintf(beststr, "%i:%02i:%02i.%02i", G_TicsToHours(besttime), G_TicsToMinutes(besttime, false), G_TicsToSeconds(besttime), G_TicsToCentiseconds(besttime));
- V_DrawString(32, 120, V_YELLOWMAP, "TIME:");
- V_DrawRightAlignedString(BASEVIDWIDTH-32, 120, 0, beststr);
- if (mapsunfinished[1])
- V_DrawRightAlignedString(BASEVIDWIDTH-32, 128, V_REDMAP, va("(%d unfinished)", mapsunfinished[1]));
-
- sprintf(beststr, "%u", bestrings);
- V_DrawString(32, 140, V_YELLOWMAP, "RINGS:");
- V_DrawRightAlignedString(BASEVIDWIDTH-32, 140, 0, beststr);
- if (mapsunfinished[2])
- V_DrawRightAlignedString(BASEVIDWIDTH-32, 148, V_REDMAP, va("(%d unfinished)", mapsunfinished[2]));
-}
-
-static void M_HandleGameStats(INT32 choice)
-{
- boolean exitmenu = false; // exit to previous menu
-
- switch (choice)
- {
- case KEY_ESCAPE:
- exitmenu = true;
- break;
-
- case KEY_ENTER:
- S_StartSound(NULL, sfx_menu1);
- M_SetupNextMenu(&SP_LevelStatsDef);
- break;
}
if (exitmenu)
{
@@ -5267,11 +6378,9 @@ void M_DrawTimeAttackMenu(void)
{
INT32 i, x, y, cursory = 0;
UINT16 dispstatus;
- patch_t *PictureOfLevel, *PictureOfUrFace;
- lumpnum_t lumpnum;
- char beststr[40];
+ patch_t *PictureOfUrFace;
- S_ChangeMusicInternal("racent", true); // Eww, but needed for when user hits escape during demo playback
+ S_ChangeMusicInternal("_inter", true); // Eww, but needed for when user hits escape during demo playback
V_DrawPatchFill(W_CachePatchName("SRB2BACK", PU_CACHE));
@@ -5313,16 +6422,6 @@ void M_DrawTimeAttackMenu(void)
V_DrawScaledPatch(currentMenu->x - 24, cursory, 0, W_CachePatchName("M_CURSOR", PU_CACHE));
V_DrawString(currentMenu->x, cursory, V_YELLOWMAP, currentMenu->menuitems[itemOn].text);
- // A 160x100 image of the level as entry MAPxxP
- lumpnum = W_CheckNumForName(va("%sP", G_BuildMapName(cv_nextmap.value)));
-
- if (lumpnum != LUMPERROR)
- PictureOfLevel = W_CachePatchName(va("%sP", G_BuildMapName(cv_nextmap.value)), PU_CACHE);
- else
- PictureOfLevel = W_CachePatchName("BLANKLVL", PU_CACHE);
-
- V_DrawSmallScaledPatch(208, 32, 0, PictureOfLevel);
-
// Character face!
if (W_CheckNumForName(skins[cv_chooseskin.value-1].charsel) != LUMPERROR)
{
@@ -5338,16 +6437,31 @@ void M_DrawTimeAttackMenu(void)
{
emblem_t *em;
INT32 yHeight;
+ patch_t *PictureOfLevel;
+ lumpnum_t lumpnum;
+ char beststr[40];
- V_DrawCenteredString(104, 32, 0, "* LEVEL RECORDS *");
+ M_DrawLevelPlatterHeader(32-lsheadingheight/2, cv_nextmap.string, true);
+
+ // A 160x100 image of the level as entry MAPxxP
+ lumpnum = W_CheckNumForName(va("%sP", G_BuildMapName(cv_nextmap.value)));
+
+ if (lumpnum != LUMPERROR)
+ PictureOfLevel = W_CachePatchName(va("%sP", G_BuildMapName(cv_nextmap.value)), PU_CACHE);
+ else
+ PictureOfLevel = W_CachePatchName("BLANKLVL", PU_CACHE);
+
+ V_DrawSmallScaledPatch(208, 32+lsheadingheight, 0, PictureOfLevel);
+
+ V_DrawString(104 - 72, 32+lsheadingheight/2, 0, "* LEVEL RECORDS *");
if (!mainrecords[cv_nextmap.value-1] || !mainrecords[cv_nextmap.value-1]->score)
sprintf(beststr, "(none)");
else
sprintf(beststr, "%u", mainrecords[cv_nextmap.value-1]->score);
- V_DrawString(104-72, 48, V_YELLOWMAP, "SCORE:");
- V_DrawRightAlignedString(104+72, 48, V_ALLOWLOWERCASE, beststr);
+ V_DrawString(104-72, 48+lsheadingheight/2, V_YELLOWMAP, "SCORE:");
+ V_DrawRightAlignedString(104+72, 48+lsheadingheight/2, V_ALLOWLOWERCASE, beststr);
if (!mainrecords[cv_nextmap.value-1] || !mainrecords[cv_nextmap.value-1]->time)
sprintf(beststr, "(none)");
@@ -5356,16 +6470,16 @@ void M_DrawTimeAttackMenu(void)
G_TicsToSeconds(mainrecords[cv_nextmap.value-1]->time),
G_TicsToCentiseconds(mainrecords[cv_nextmap.value-1]->time));
- V_DrawString(104-72, 58, V_YELLOWMAP, "TIME:");
- V_DrawRightAlignedString(104+72, 58, V_ALLOWLOWERCASE, beststr);
+ V_DrawString(104-72, 58+lsheadingheight/2, V_YELLOWMAP, "TIME:");
+ V_DrawRightAlignedString(104+72, 58+lsheadingheight/2, V_ALLOWLOWERCASE, beststr);
if (!mainrecords[cv_nextmap.value-1] || !mainrecords[cv_nextmap.value-1]->rings)
sprintf(beststr, "(none)");
else
sprintf(beststr, "%hu", mainrecords[cv_nextmap.value-1]->rings);
- V_DrawString(104-72, 68, V_YELLOWMAP, "RINGS:");
- V_DrawRightAlignedString(104+72, 68, V_ALLOWLOWERCASE, beststr);
+ V_DrawString(104-72, 68+lsheadingheight/2, V_YELLOWMAP, "RINGS:");
+ V_DrawRightAlignedString(104+72, 68+lsheadingheight/2, V_ALLOWLOWERCASE, beststr);
// Draw record emblems.
em = M_GetLevelEmblems(cv_nextmap.value);
@@ -5381,17 +6495,17 @@ void M_DrawTimeAttackMenu(void)
}
if (em->collected)
- V_DrawSmallMappedPatch(104+76, yHeight, 0, W_CachePatchName(M_GetEmblemPatch(em), PU_CACHE),
+ V_DrawSmallMappedPatch(104+76, yHeight+lsheadingheight/2, 0, W_CachePatchName(M_GetEmblemPatch(em), PU_CACHE),
R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(em), GTC_CACHE));
else
- V_DrawSmallScaledPatch(104+76, yHeight, 0, W_CachePatchName("NEEDIT", PU_CACHE));
+ V_DrawSmallScaledPatch(104+76, yHeight+lsheadingheight/2, 0, W_CachePatchName("NEEDIT", PU_CACHE));
skipThisOne:
em = M_GetLevelEmblems(-1);
}
}
- // ALWAYS DRAW level name and skin even when not on this menu!
+ // ALWAYS DRAW level and skin even when not on this menu!
if (currentMenu != &SP_TimeAttackDef)
{
consvar_t *ncv;
@@ -5399,27 +6513,30 @@ void M_DrawTimeAttackMenu(void)
x = SP_TimeAttackDef.x;
y = SP_TimeAttackDef.y;
- for (i = 0; i < 2; ++i)
- {
- ncv = (consvar_t *)SP_TimeAttackMenu[i].itemaction;
+ V_DrawString(x, y + SP_TimeAttackMenu[talevel].alphaKey, V_TRANSLUCENT, SP_TimeAttackMenu[talevel].text);
- V_DrawString(x, y + SP_TimeAttackMenu[i].alphaKey, V_TRANSLUCENT, SP_TimeAttackMenu[i].text);
- V_DrawString(BASEVIDWIDTH - x - V_StringWidth(ncv->string, 0),
- y + SP_TimeAttackMenu[i].alphaKey, V_YELLOWMAP|V_TRANSLUCENT, ncv->string);
- }
+ ncv = (consvar_t *)SP_TimeAttackMenu[taplayer].itemaction;
+ V_DrawString(x, y + SP_TimeAttackMenu[taplayer].alphaKey, V_TRANSLUCENT, SP_TimeAttackMenu[taplayer].text);
+ V_DrawString(BASEVIDWIDTH - x - V_StringWidth(ncv->string, 0), y + SP_TimeAttackMenu[taplayer].alphaKey, V_YELLOWMAP|V_TRANSLUCENT, ncv->string);
}
}
+static void M_TimeAttackLevelSelect(INT32 choice)
+{
+ (void)choice;
+ SP_TimeAttackLevelSelectDef.prevMenu = currentMenu;
+ M_SetupNextMenu(&SP_TimeAttackLevelSelectDef);
+}
+
// Going to Time Attack menu...
static void M_TimeAttack(INT32 choice)
{
(void)choice;
- memset(skins_cons_t, 0, sizeof (skins_cons_t));
-
+ SP_TimeAttackDef.prevMenu = &MainDef;
levellistmode = LLM_RECORDATTACK; // Don't be dependent on cv_newgametype
- if (M_CountLevelsToShowInList() == 0)
+ if (!M_PrepareLevelPlatter(-1))
{
M_StartMessage(M_GetText("No record-attackable levels found.\n"),NULL,MM_NOTHING);
return;
@@ -5427,119 +6544,158 @@ static void M_TimeAttack(INT32 choice)
M_PatchSkinNameTable();
- M_PrepareLevelSelect();
M_SetupNextMenu(&SP_TimeAttackDef);
- Nextmap_OnChange();
-
- itemOn = tastart; // "Start" is selected.
+ if (!M_CanShowLevelInList(cv_nextmap.value-1, -1) && levelselect.rows[0].maplist[0])
+ CV_SetValue(&cv_nextmap, levelselect.rows[0].maplist[0]);
+ else
+ Nextmap_OnChange();
G_SetGamestate(GS_TIMEATTACK);
- S_ChangeMusicInternal("racent", true);
+ S_ChangeMusicInternal("_inter", true);
+
+ itemOn = tastart; // "Start" is selected.
}
// Drawing function for Nights Attack
void M_DrawNightsAttackMenu(void)
{
- patch_t *PictureOfLevel;
- lumpnum_t lumpnum;
- char beststr[40];
+ INT32 i, x, y, cursory = 0;
+ UINT16 dispstatus;
- S_ChangeMusicInternal("racent", true); // Eww, but needed for when user hits escape during demo playback
+ S_ChangeMusicInternal("_inter", true); // Eww, but needed for when user hits escape during demo playback
V_DrawPatchFill(W_CachePatchName("SRB2BACK", PU_CACHE));
+ M_DrawMenuTitle();
+
// draw menu (everything else goes on top of it)
- M_DrawGenericMenu();
+ // Sadly we can't just use generic mode menus because we need some extra hacks
+ x = currentMenu->x;
+ y = currentMenu->y;
- // A 160x100 image of the level as entry MAPxxP
- lumpnum = W_CheckNumForName(va("%sP", G_BuildMapName(cv_nextmap.value)));
+ for (i = 0; i < currentMenu->numitems; ++i)
+ {
+ dispstatus = (currentMenu->menuitems[i].status & IT_DISPLAY);
+ if (dispstatus != IT_STRING && dispstatus != IT_WHITESTRING)
+ continue;
- if (lumpnum != LUMPERROR)
- PictureOfLevel = W_CachePatchName(va("%sP", G_BuildMapName(cv_nextmap.value)), PU_CACHE);
- else
- PictureOfLevel = W_CachePatchName("BLANKLVL", PU_CACHE);
+ y = currentMenu->y+currentMenu->menuitems[i].alphaKey;
+ if (i == itemOn)
+ cursory = y;
- V_DrawSmallScaledPatch(90, 28, 0, PictureOfLevel);
+ V_DrawString(x, y, (dispstatus == IT_WHITESTRING) ? V_YELLOWMAP : 0 , currentMenu->menuitems[i].text);
+
+ // Cvar specific handling
+ if ((currentMenu->menuitems[i].status & IT_TYPE) == IT_CVAR)
+ {
+ consvar_t *cv = (consvar_t *)currentMenu->menuitems[i].itemaction;
+ INT32 soffset = 0;
+
+ // hack to keep the menu from overlapping the overall grade icon
+ if (currentMenu != &SP_NightsAttackDef)
+ soffset = 80;
+
+ // Should see nothing but strings
+ V_DrawString(BASEVIDWIDTH - x - soffset - V_StringWidth(cv->string, 0), y, V_YELLOWMAP, cv->string);
+ }
+ }
+
+ // DRAW THE SKULL CURSOR
+ V_DrawScaledPatch(currentMenu->x - 24, cursory, 0, W_CachePatchName("M_CURSOR", PU_CACHE));
+ V_DrawString(currentMenu->x, cursory, V_YELLOWMAP, currentMenu->menuitems[itemOn].text);
// Level record list
if (cv_nextmap.value)
{
emblem_t *em;
INT32 yHeight;
+ patch_t *PictureOfLevel;
+ lumpnum_t lumpnum;
+ char beststr[40];
UINT8 bestoverall = G_GetBestNightsGrade(cv_nextmap.value, 0);
UINT8 bestgrade = G_GetBestNightsGrade(cv_nextmap.value, cv_dummymares.value);
UINT32 bestscore = G_GetBestNightsScore(cv_nextmap.value, cv_dummymares.value);
tic_t besttime = G_GetBestNightsTime(cv_nextmap.value, cv_dummymares.value);
- if (P_HasGrades(cv_nextmap.value, 0))
- V_DrawScaledPatch(200, 28 + 8, 0, ngradeletters[bestoverall]);
+ M_DrawLevelPlatterHeader(32-lsheadingheight/2, cv_nextmap.string, true);
- if (currentMenu == &SP_NightsAttackDef)
- {
- if (P_HasGrades(cv_nextmap.value, cv_dummymares.value))
- {
- V_DrawString(160-88, 112, V_YELLOWMAP, "BEST GRADE:");
- V_DrawSmallScaledPatch(160 + 86 - (ngradeletters[bestgrade]->width/2),
- 112 + 8 - (ngradeletters[bestgrade]->height/2),
- 0, ngradeletters[bestgrade]);
- }
+ // A 160x100 image of the level as entry MAPxxP
+ lumpnum = W_CheckNumForName(va("%sP", G_BuildMapName(cv_nextmap.value)));
- if (!bestscore)
- sprintf(beststr, "(none)");
- else
- sprintf(beststr, "%u", bestscore);
-
- V_DrawString(160 - 88, 122, V_YELLOWMAP, "BEST SCORE:");
- V_DrawRightAlignedString(160 + 88, 122, V_ALLOWLOWERCASE, beststr);
-
- if (besttime == UINT32_MAX)
- sprintf(beststr, "(none)");
- else
- sprintf(beststr, "%i:%02i.%02i", G_TicsToMinutes(besttime, true),
- G_TicsToSeconds(besttime),
- G_TicsToCentiseconds(besttime));
-
- V_DrawString(160-88, 132, V_YELLOWMAP, "BEST TIME:");
- V_DrawRightAlignedString(160+88, 132, V_ALLOWLOWERCASE, beststr);
-
- if (cv_dummymares.value == 0) {
- // Draw record emblems.
- em = M_GetLevelEmblems(cv_nextmap.value);
- while (em)
- {
- switch (em->type)
- {
- case ET_NGRADE: yHeight = 112; break;
- case ET_NTIME: yHeight = 132; break;
- default:
- goto skipThisOne;
- }
-
- if (em->collected)
- V_DrawSmallMappedPatch(160+88, yHeight, 0, W_CachePatchName(M_GetEmblemPatch(em), PU_CACHE),
- R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(em), GTC_CACHE));
- else
- V_DrawSmallScaledPatch(160+88, yHeight, 0, W_CachePatchName("NEEDIT", PU_CACHE));
-
- skipThisOne:
- em = M_GetLevelEmblems(-1);
- }
- }
- }
- // ALWAYS DRAW level name even when not on this menu!
+ if (lumpnum != LUMPERROR)
+ PictureOfLevel = W_CachePatchName(va("%sP", G_BuildMapName(cv_nextmap.value)), PU_CACHE);
else
- {
- consvar_t *ncv;
- INT32 x = SP_NightsAttackDef.x;
- INT32 y = SP_NightsAttackDef.y;
+ PictureOfLevel = W_CachePatchName("BLANKLVL", PU_CACHE);
- ncv = (consvar_t *)SP_NightsAttackMenu[0].itemaction;
- V_DrawString(x, y + SP_NightsAttackMenu[0].alphaKey, V_TRANSLUCENT, SP_NightsAttackMenu[0].text);
- V_DrawString(BASEVIDWIDTH - x - V_StringWidth(ncv->string, 0),
- y + SP_NightsAttackMenu[0].alphaKey, V_YELLOWMAP|V_TRANSLUCENT, ncv->string);
+ V_DrawSmallScaledPatch(208, 32+lsheadingheight, 0, PictureOfLevel);
+
+ V_DrawString(104 - 72, 32+lsheadingheight/2, 0, "* LEVEL RECORDS *");
+
+ if (P_HasGrades(cv_nextmap.value, 0))
+ V_DrawScaledPatch(235, 135, 0, ngradeletters[bestoverall]);
+
+ if (P_HasGrades(cv_nextmap.value, cv_dummymares.value))
+ {//make bigger again
+ V_DrawString(104 - 72, 48+lsheadingheight/2, V_YELLOWMAP, "BEST GRADE:");
+ V_DrawSmallScaledPatch(104 + 72 - (ngradeletters[bestgrade]->width/2),
+ 48+lsheadingheight/2 + 8 - (ngradeletters[bestgrade]->height/2),
+ 0, ngradeletters[bestgrade]);
+ }
+
+ if (!bestscore)
+ sprintf(beststr, "(none)");
+ else
+ sprintf(beststr, "%u", bestscore);
+
+ V_DrawString(104 - 72, 58+lsheadingheight/2, V_YELLOWMAP, "BEST SCORE:");
+ V_DrawRightAlignedString(104 + 72, 58+lsheadingheight/2, V_ALLOWLOWERCASE, beststr);
+
+ if (besttime == UINT32_MAX)
+ sprintf(beststr, "(none)");
+ else
+ sprintf(beststr, "%i:%02i.%02i", G_TicsToMinutes(besttime, true),
+ G_TicsToSeconds(besttime),
+ G_TicsToCentiseconds(besttime));
+
+ V_DrawString(104 - 72, 68+lsheadingheight/2, V_YELLOWMAP, "BEST TIME:");
+ V_DrawRightAlignedString(104 + 72, 68+lsheadingheight/2, V_ALLOWLOWERCASE, beststr);
+
+ if (cv_dummymares.value == 0) {
+ // Draw record emblems.
+ em = M_GetLevelEmblems(cv_nextmap.value);
+ while (em)
+ {
+ switch (em->type)
+ {
+ case ET_NGRADE: yHeight = 48; break;
+ case ET_NTIME: yHeight = 68; break;
+ default:
+ goto skipThisOne;
+ }
+
+ if (em->collected)
+ V_DrawSmallMappedPatch(104+76, yHeight+lsheadingheight/2, 0, W_CachePatchName(M_GetEmblemPatch(em), PU_CACHE),
+ R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(em), GTC_CACHE));
+ else
+ V_DrawSmallScaledPatch(104+76, yHeight+lsheadingheight/2, 0, W_CachePatchName("NEEDIT", PU_CACHE));
+
+ skipThisOne:
+ em = M_GetLevelEmblems(-1);
+ }
}
}
+
+ // ALWAYS DRAW level even when not on this menu!
+ if (currentMenu != &SP_NightsAttackDef)
+ V_DrawString(SP_NightsAttackDef.x, SP_NightsAttackDef.y + SP_TimeAttackMenu[nalevel].alphaKey, V_TRANSLUCENT, SP_NightsAttackMenu[nalevel].text);
+}
+
+static void M_NightsAttackLevelSelect(INT32 choice)
+{
+ (void)choice;
+ SP_NightsAttackLevelSelectDef.prevMenu = currentMenu;
+ M_SetupNextMenu(&SP_NightsAttackLevelSelectDef);
}
// Going to Nights Attack menu...
@@ -5547,27 +6703,27 @@ static void M_NightsAttack(INT32 choice)
{
(void)choice;
- memset(skins_cons_t, 0, sizeof (skins_cons_t));
-
+ SP_NightsAttackDef.prevMenu = &MainDef;
levellistmode = LLM_NIGHTSATTACK; // Don't be dependent on cv_newgametype
- if (M_CountLevelsToShowInList() == 0)
+ if (!M_PrepareLevelPlatter(-1))
{
M_StartMessage(M_GetText("No NiGHTS-attackable levels found.\n"),NULL,MM_NOTHING);
return;
}
-
// This is really just to make sure Sonic is the played character, just in case
M_PatchSkinNameTable();
- M_PrepareLevelSelect();
M_SetupNextMenu(&SP_NightsAttackDef);
- Nextmap_OnChange();
-
- itemOn = nastart; // "Start" is selected.
+ if (!M_CanShowLevelInList(cv_nextmap.value-1, -1) && levelselect.rows[0].maplist[0])
+ CV_SetValue(&cv_nextmap, levelselect.rows[0].maplist[0]);
+ else
+ Nextmap_OnChange();
G_SetGamestate(GS_TIMEATTACK);
- S_ChangeMusicInternal("racent", true);
+ S_ChangeMusicInternal("_inter", true);
+
+ itemOn = nastart; // "Start" is selected.
}
// Player has selected the "START" from the nights attack screen
@@ -5683,8 +6839,7 @@ static void M_EraseGuest(INT32 choice)
M_SetupNextMenu(&SP_NightsAttackDef);
else
M_SetupNextMenu(&SP_TimeAttackDef);
- CV_AddValue(&cv_nextmap, -1);
- CV_AddValue(&cv_nextmap, 1);
+ Nextmap_OnChange();
M_StartMessage(M_GetText("Guest replay data erased.\n"),NULL,MM_NOTHING);
}
@@ -5710,8 +6865,7 @@ static void M_OverwriteGuest(const char *which, boolean nights)
M_SetupNextMenu(&SP_NightsAttackDef);
else
M_SetupNextMenu(&SP_TimeAttackDef);
- CV_AddValue(&cv_nextmap, -1);
- CV_AddValue(&cv_nextmap, 1);
+ Nextmap_OnChange();
M_StartMessage(M_GetText("Guest replay data saved.\n"),NULL,MM_NOTHING);
}
@@ -5801,10 +6955,8 @@ static void M_ModeAttackEndGame(INT32 choice)
itemOn = currentMenu->lastOn;
G_SetGamestate(GS_TIMEATTACK);
modeattacking = ATTACKING_NONE;
- S_ChangeMusicInternal("racent", true);
- // Update replay availability.
- CV_AddValue(&cv_nextmap, 1);
- CV_AddValue(&cv_nextmap, -1);
+ S_ChangeMusicInternal("_inter", true);
+ Nextmap_OnChange();
}
// ========
@@ -6176,25 +7328,6 @@ static void M_ChooseRoom(INT32 choice)
// Start Server Menu
//===========================================================================
-//
-// FindFirstMap
-//
-// Finds the first map of a particular gametype
-// Defaults to 1 if nothing found.
-//
-static INT32 M_FindFirstMap(INT32 gtype)
-{
- INT32 i;
-
- for (i = 0; i < NUMMAPS; i++)
- {
- if (mapheaderinfo[i] && (mapheaderinfo[i]->typeoflevel & gtype))
- return i + 1;
- }
-
- return 1;
-}
-
static void M_StartServer(INT32 choice)
{
boolean StartSplitScreenGame = (currentMenu == &MP_SplitServerDef);
@@ -6235,15 +7368,13 @@ static void M_StartServer(INT32 choice)
static void M_DrawServerMenu(void)
{
- lumpnum_t lumpnum;
- patch_t *PictureOfLevel;
-
M_DrawGenericMenu();
#ifndef NONET
// Room name
if (currentMenu == &MP_ServerDef)
{
+ M_DrawLevelPlatterHeader(currentMenu->y - lsheadingheight/2, "Server settings", true);
if (ms_RoomId < 0)
V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x, currentMenu->y + MP_ServerMenu[mp_server_room].alphaKey,
V_YELLOWMAP, (itemOn == mp_server_room) ? "