diff options
| author | Jakob L. Kreuze <jakob@memeware.net> | 2018-01-13 15:16:40 -0500 |
|---|---|---|
| committer | Jakob L. Kreuze <jakob@memeware.net> | 2018-01-13 15:16:40 -0500 |
| commit | 180b997b0eccd5eacff6fd2d73d1ec1af0a46a07 (patch) | |
| tree | 861fdd2f37ef4b6a85e423f3e88cfe455cd7f167 /patch | |
Initial commit.
Diffstat (limited to 'patch')
| -rw-r--r-- | patch/GNUmakefile | 1004 | ||||
| -rw-r--r-- | patch/config.cpp | 929 | ||||
| -rw-r--r-- | patch/fluidsynth_inc.h | 6 | ||||
| -rw-r--r-- | patch/game.h | 568 | ||||
| -rw-r--r-- | patch/menus.cpp | 6639 | ||||
| -rw-r--r-- | patch/osdcmds.cpp | 1773 | ||||
| -rw-r--r-- | patch/sdlmusic.cpp | 180 |
7 files changed, 11099 insertions, 0 deletions
diff --git a/patch/GNUmakefile b/patch/GNUmakefile new file mode 100644 index 0000000..1541d75 --- /dev/null +++ b/patch/GNUmakefile @@ -0,0 +1,1004 @@ +# +# EDuke32 Makefile for GNU Make +# + +include Common.mak + +### File Extensions +asm := nasm +o := o + +### Directories +source := source +obj := obj + +### Functions +define parent +$(word 1,$(subst _, ,$1)) +endef +define expandobjs +$$(addprefix $$($$(call parent,$1)_obj)/,$$(addsuffix .$$o,$$(basename $$($1_objs) $$($1_rsrc_objs) $$($1_gen_objs)))) +endef +define expandsrcs +$(addprefix $($(call parent,$1)_src)/,$($1_objs)) $(addprefix $($(call parent,$1)_rsrc)/,$($1_rsrc_objs)) $(addprefix $($(call parent,$1)_obj)/,$($1_gen_objs)) +endef +define expanddeps +$(strip $1 $(foreach j,$1,$(call $0,$($j_deps)))) +endef +define getdeps +$(call expanddeps,$1_$2 $(common_$2_deps) engine) +endef + + +##### External Library Definitions + +#### libxmp-lite + +libxmplite := libxmp-lite + +libxmplite_objs := \ + control.c \ + dataio.c \ + effects.c \ + filter.c \ + format.c \ + hio.c \ + lfo.c \ + load.c \ + load_helpers.c \ + memio.c \ + mixer.c \ + mix_all.c \ + period.c \ + player.c \ + read_event.c \ + scan.c \ + smix.c \ + virtual.c \ + common.c \ + itsex.c \ + it_load.c \ + mod_load.c \ + mtm_load.c \ + s3m_load.c \ + sample.c \ + xm_load.c \ + +libxmplite_root := $(source)/$(libxmplite) +libxmplite_src := $(libxmplite_root)/src +libxmplite_inc := $(libxmplite_root)/include +libxmplite_obj := $(obj)/$(libxmplite) + +libxmplite_cflags := -DHAVE_ROUND -DLIBXMP_CORE_PLAYER -DBUILDING_STATIC -I$(libxmplite_inc)/libxmp-lite -Wno-unused-parameter -Wno-sign-compare + + +#### LPeg + +lpeg := lpeg + +lpeg_objs := \ + lpcap.c \ + lpcode.c \ + lpprint.c \ + lptree.c \ + lpvm.c \ + +lpeg_root := $(source)/$(lpeg) +lpeg_src := $(lpeg_root)/src +lpeg_inc := $(lpeg_root)/include +lpeg_obj := $(obj)/$(lpeg) + + +#### ENet + +enet := enet + +enet_objs := \ + callbacks.c \ + host.c \ + list.c \ + packet.c \ + peer.c \ + protocol.c \ + compress.c \ + +enet_root := $(source)/$(enet) +enet_src := $(enet_root)/src +enet_inc := $(enet_root)/include +enet_obj := $(obj)/$(enet) + +enet_cflags := + +ifeq ($(PLATFORM),WINDOWS) + enet_objs += win32.c +else + enet_objs += unix.c + enet_cflags += -DHAS_SOCKLEN_T +endif + + +##### Component Definitions + +#### EBacktrace + +ifndef ebacktrace_dll + ebacktrace_dll := ebacktrace1.dll + ifeq ($(findstring x86_64,$(COMPILERTARGET)),x86_64) + ebacktrace_dll := ebacktrace1-64.dll + endif +endif + + +#### BUILD Engine + +engine := build + +engine_root := $(source)/$(engine) +engine_src := $(engine_root)/src +engine_inc := $(engine_root)/include +engine_obj := $(obj)/$(engine) + +engine_cflags := -I$(engine_src) + +engine_objs := \ + rev.cpp \ + baselayer.cpp \ + cache1d.cpp \ + common.cpp \ + compat.cpp \ + crc32.cpp \ + defs.cpp \ + engine.cpp \ + tiles.cpp \ + clip.cpp \ + 2d.cpp \ + hash.cpp \ + palette.cpp \ + polymost.cpp \ + texcache.cpp \ + dxtfilter.cpp \ + hightile.cpp \ + textfont.cpp \ + smalltextfont.cpp \ + kplib.cpp \ + lz4.c \ + osd.cpp \ + pragmas.cpp \ + scriptfile.cpp \ + mmulti_null.cpp \ + mutex.cpp \ + xxhash.c \ + md4.cpp \ + colmatch.cpp \ + screenshot.cpp \ + mhk.cpp \ + pngwrite.cpp \ + miniz.c \ + +engine_editor_objs := \ + build.cpp \ + config.cpp \ + defs.cpp \ + +engine_tools_objs := \ + compat.cpp \ + pragmas.cpp \ + kplib.cpp \ + cache1d.cpp \ + crc32.cpp \ + colmatch.cpp \ + +ifeq (0,$(NOASM)) + engine_objs += a.nasm +else + engine_objs += a-c.cpp + ifneq (0,$(USE_ASM64)) + engine_objs += a64.yasm + endif +endif +ifeq (1,$(USE_OPENGL)) + engine_objs += glbuild.cpp voxmodel.cpp mdsprite.cpp + ifeq (1,$(POLYMER)) + engine_objs += polymer.cpp + endif +endif +ifneq (0,$(LUNATIC)) + engine_objs += lunatic.cpp +endif +ifeq ($(PLATFORM),DARWIN) + engine_objs += osxbits.mm + engine_tools_objs += osxbits.mm + ifeq ($(STARTUP_WINDOW),1) + engine_editor_objs += startosx.editor.mm + endif + ifeq ($(SDL_TARGET),1) + ifneq ($(SDL_FRAMEWORK),0) + engine_objs += SDLMain.mm + endif + endif +endif +ifeq ($(PLATFORM),WINDOWS) + engine_objs += winbits.cpp + ifeq ($(STARTUP_WINDOW),1) + engine_editor_objs += startwin.editor.cpp + endif +endif +ifeq ($(PLATFORM),WII) + engine_objs += wiibits.cpp + LINKERFLAGS += -Wl,-wrap,c_default_exceptionhandler +endif +ifeq ($(RENDERTYPE),SDL) + engine_objs += sdlayer.cpp + + ifeq (1,$(HAVE_GTK2)) + engine_objs += gtkbits.cpp dynamicgtk.cpp + ifeq ($(STARTUP_WINDOW),1) + engine_editor_objs += startgtk.editor.cpp + endif + endif +endif +ifeq ($(RENDERTYPE),WIN) + engine_objs += winlayer.cpp rawinput.cpp +endif + +ifneq ($(USE_LIBVPX),0) + engine_objs += animvpx.cpp +endif + + +#### mact + +mact := mact + +mact_root := $(source)/$(mact) +mact_src := $(mact_root)/src +mact_inc := $(mact_root)/include +mact_obj := $(obj)/$(mact) + +mact_objs := \ + file_lib.cpp \ + control.cpp \ + keyboard.cpp \ + joystick.cpp \ + scriplib.cpp \ + animlib.cpp \ + + +#### AudioLib + +audiolib := audiolib + +audiolib_objs := \ + drivers.cpp \ + fx_man.cpp \ + multivoc.cpp \ + mix.cpp \ + mixst.cpp \ + pitch.cpp \ + formats.cpp \ + vorbis.cpp \ + flac.cpp \ + xa.cpp \ + xmp.cpp \ + driver_nosound.cpp \ + +audiolib_root := $(source)/$(audiolib) +audiolib_src := $(audiolib_root)/src +audiolib_inc := $(audiolib_root)/include +audiolib_obj := $(obj)/$(audiolib) + +audiolib_cflags := + +audiolib_deps := + +ifeq ($(PLATFORM),WINDOWS) + ifeq ($(MIXERTYPE),WIN) + audiolib_objs += driver_directsound.cpp + endif +endif + +ifeq ($(MIXERTYPE),SDL) + ifeq (,$(filter $(PLATFORM),DARWIN WINDOWS WII)) + audiolib_cflags += `$(PKG_CONFIG) --cflags vorbis` + endif + audiolib_objs += driver_sdl.cpp +endif + +ifneq (0,$(HAVE_XMP)) + audiolib_cflags += -I$(libxmplite_inc) + audiolib_deps += libxmplite +endif + + +#### Tools + +tools := tools + +tools_objs := \ + compat_tools.cpp \ + +tools_root := $(source)/$(tools) +tools_src := $(tools_root)/src +tools_obj := $(obj)/$(tools) + +tools_cflags := $(engine_cflags) + +tools_deps := engine_tools + +tools_targets := \ + kextract \ + kgroup \ + transpal \ + wad2art \ + wad2map \ + kmd2tool \ + md2tool \ + generateicon \ + cacheinfo \ + arttool \ + givedepth \ + mkpalette \ + unpackssi \ + bsuite \ + ivfrate \ + map2stl \ + +ifeq ($(PLATFORM),WINDOWS) + tools_targets += enumdisplay getdxdidf +endif +ifeq ($(RENDERTYPE),SDL) + tools_targets += makesdlkeytrans +endif + + +#### KenBuild (Test Game) + +kenbuild := kenbuild + +kenbuild_root := $(source)/$(kenbuild) +kenbuild_src := $(kenbuild_root)/src +kenbuild_rsrc := $(kenbuild_root)/rsrc +kenbuild_obj := $(obj)/$(kenbuild) + +kenbuild_cflags := -I$(kenbuild_src) + +kenbuild_game := ekenbuild +kenbuild_editor := ekenbuild-editor + +kenbuild_game_proper := EKenBuild +kenbuild_editor_proper := EKenBuild Editor + +kenbuild_game_objs := \ + game.cpp \ + sound_stub.cpp \ + common.cpp \ + config.cpp \ + +kenbuild_editor_objs := \ + bstub.cpp \ + common.cpp \ + +kenbuild_game_rsrc_objs := +kenbuild_editor_rsrc_objs := +kenbuild_game_gen_objs := +kenbuild_editor_rsrc_objs := + +ifeq (1,$(HAVE_GTK2)) + kenbuild_game_objs += startgtk.game.cpp + kenbuild_game_gen_objs += game_banner.c + kenbuild_editor_gen_objs += build_banner.c +endif +ifeq ($(RENDERTYPE),SDL) + kenbuild_game_rsrc_objs += game_icon.c + kenbuild_editor_rsrc_objs += build_icon.c +endif +ifeq ($(PLATFORM),WINDOWS) + kenbuild_game_objs += startwin.game.cpp + kenbuild_game_rsrc_objs += gameres.rc + kenbuild_editor_rsrc_objs += buildres.rc +endif + +ifeq ($(PLATFORM),DARWIN) + ifeq ($(STARTUP_WINDOW),1) + kenbuild_game_objs += StartupWinController.game.mm + endif +endif + + +#### Duke Nukem 3D + +duke3d := duke3d + +duke3d_game_ldflags := +duke3d_editor_ldflags := + +duke3d_game_stripflags := +duke3d_editor_stripflags := + +duke3d_root := $(source)/$(duke3d) +duke3d_src := $(duke3d_root)/src +duke3d_rsrc := $(duke3d_root)/rsrc +duke3d_obj := $(obj)/$(duke3d) + +duke3d_cflags := -I$(duke3d_src) + +common_editor_deps := duke3d_common_editor engine_editor + +duke3d_game_deps := duke3d_common_midi audiolib mact +duke3d_editor_deps := audiolib + +ifneq (0,$(NETCODE)) + duke3d_game_deps += enet +endif + +ifneq (0,$(LUNATIC)) + duke3d_game_deps += lunatic lunatic_game lpeg + duke3d_editor_deps += lunatic lunatic_editor lpeg +endif + +duke3d_game := eduke32 +duke3d_editor := mapster32 + +ifneq (,$(APPBASENAME)) + duke3d_game := $(APPBASENAME) +endif + +duke3d_game_proper := EDuke32 +duke3d_editor_proper := Mapster32 + +duke3d_common_editor_objs := \ + m32common.cpp \ + m32def.cpp \ + m32exec.cpp \ + m32vars.cpp \ + +duke3d_game_objs := \ + game.cpp \ + global.cpp \ + actors.cpp \ + gamedef.cpp \ + gameexec.cpp \ + gamevars.cpp \ + player.cpp \ + premap.cpp \ + sector.cpp \ + anim.cpp \ + common.cpp \ + config.cpp \ + demo.cpp \ + input.cpp \ + menus.cpp \ + namesdyn.cpp \ + net.cpp \ + savegame.cpp \ + rts.cpp \ + osdfuncs.cpp \ + osdcmds.cpp \ + grpscan.cpp \ + sounds.cpp \ + soundsdyn.cpp \ + cheats.cpp \ + sbar.cpp \ + screentext.cpp \ + screens.cpp \ + cmdline.cpp \ + +duke3d_editor_objs := \ + astub.cpp \ + common.cpp \ + grpscan.cpp \ + sounds_mapster32.cpp \ + +duke3d_game_rsrc_objs := +duke3d_editor_rsrc_objs := +duke3d_game_gen_objs := +duke3d_editor_gen_objs := + +duke3d_game_miscdeps := +duke3d_editor_miscdeps := +duke3d_game_orderonlydeps := +duke3d_editor_orderonlydeps := + +## Lunatic devel +lunatic := lunatic +lunatic_src := $(duke3d_src)/$(lunatic) +lunatic_obj := $(duke3d_obj) + +ifneq (0,$(LUNATIC)) + COMPILERFLAGS += -I$(lunatic_src) -DLUNATIC + + # Determine size of _defs*.lua bytecode once. + ifndef DEFS_BC_SIZE + DEFS_BC_SIZE := $(shell $(LUAJIT) -bg -t h $(lunatic_src)/_defs_game.lua -) + DEFS_BC_SIZE := $(word 3, $(DEFS_BC_SIZE)) + endif + ifndef DEFS_M32_BC_SIZE + DEFS_M32_BC_SIZE := $(shell $(LUAJIT) -bg -t h $(lunatic_src)/_defs_editor.lua -) + DEFS_M32_BC_SIZE := $(word 3, $(DEFS_M32_BC_SIZE)) + endif + duke3d_cflags += -DLUNATIC_DEFS_BC_SIZE=$(DEFS_BC_SIZE) -DLUNATIC_DEFS_M32_BC_SIZE=$(DEFS_M32_BC_SIZE) + + # Lunatic object base names. These are not used in targets directly. + lunatic_objs := \ + defs_common.lua \ + engine_maptext.lua \ + engine.lua \ + bcarray.lua \ + bcheck.lua \ + bitar.lua \ + xmath.lua \ + v.lua \ + dump.lua \ + dis_x86.lua \ + dis_x64.lua \ + + lunatic_game_objs := \ + lunatic_game.cpp \ + _defs_game.lua \ + con_lang.lua \ + lunacon.lua \ + randgen.lua \ + stat.lua \ + control.lua \ + lunasave.lua \ + fs.lua \ + + lunatic_editor_objs := \ + lunatic_editor.cpp \ + _defs_editor.lua \ + + # TODO: remove debugging modules from release build + + # now, take care of having the necessary symbols (sector, wall, etc.) in the + # executable no matter what the debugging level + + ifeq ($(PLATFORM),DARWIN) + # strip on OSX says: removing global symbols from a final linked no longer supported. + # Use -exported_symbols_list at link time when building + # But, following _their_ directions does not give us the symbols! wtf? + # Instead of using -alias_list and -exported_symbols_list, prevent stripping them. + duke3d_game_stripflags += -s $(duke3d_obj)/lunatic_dynsymlist_game_osx + duke3d_editor_stripflags += -s $(duke3d_obj)/lunatic_dynsymlist_editor_osx + + duke3d_game_orderonlydeps += $(duke3d_obj)/lunatic_dynsymlist_game_osx + duke3d_editor_orderonlydeps += $(duke3d_obj)/lunatic_dynsymlist_editor_osx + LINKERFLAGS += -pagezero_size 10000 -image_base 100000000 + endif + ifeq ($(PLATFORM),WINDOWS) + override STRIP := + duke3d_game_miscdeps += $(duke3d_obj)/lunatic_dynsymlist_game.def + duke3d_editor_miscdeps += $(duke3d_obj)/lunatic_dynsymlist_editor.def + endif + ifeq ($(SUBPLATFORM),LINUX) + override STRIP := + duke3d_game_ldflags += -Wl,--dynamic-list=$(lunatic_src)/dynsymlist_game.lds + duke3d_editor_ldflags += -Wl,--dynamic-list=$(lunatic_src)/dynsymlist_editor.lds + endif +endif + +ifeq ($(SUBPLATFORM),LINUX) + LIBS += -lFLAC -lvorbisfile -lvorbis -logg +endif + +ifeq ($(PLATFORM),BSD) + LIBS += -lFLAC -lvorbisfile -lvorbis -logg -lexecinfo +endif + +ifeq ($(PLATFORM),DARWIN) + LIBS += -lFLAC -lvorbisfile -lvorbis -logg -lm \ + -Wl,-framework,Cocoa -Wl,-framework,Carbon -Wl,-framework,OpenGL \ + -Wl,-framework,CoreMIDI -Wl,-framework,AudioUnit \ + -Wl,-framework,AudioToolbox -Wl,-framework,IOKit -Wl,-framework,AGL + ifneq (00,$(DARWIN9)$(DARWIN10)) + LIBS += -Wl,-framework,QuickTime -lm + endif + + ifeq ($(STARTUP_WINDOW),1) + duke3d_game_objs += GrpFile.game.mm GameListSource.game.mm startosx.game.mm + endif +endif + +ifeq ($(PLATFORM),WINDOWS) + LIBS += -lFLAC -lvorbisfile -lvorbis -logg + duke3d_game_objs += winbits.cpp + duke3d_game_rsrc_objs += gameres.rc + duke3d_editor_rsrc_objs += buildres.rc + ifeq ($(STARTUP_WINDOW),1) + duke3d_game_objs += startwin.game.cpp + endif + ifeq ($(MIXERTYPE),WIN) + LIBS += -ldsound + duke3d_common_midi_objs := music.cpp midi.cpp mpu401.cpp + endif +endif + +ifeq ($(PLATFORM),WII) + LIBS += -lvorbisidec +endif + +ifeq (11,$(HAVE_GTK2)$(STARTUP_WINDOW)) + duke3d_game_objs += startgtk.game.cpp + duke3d_game_gen_objs += game_banner.c + duke3d_editor_gen_objs += build_banner.c +endif +ifeq ($(RENDERTYPE),SDL) + duke3d_game_rsrc_objs += game_icon.c + duke3d_editor_rsrc_objs += build_icon.c +endif +ifeq ($(MIXERTYPE),SDL) + LIBS += -lfluidsynth + duke3d_common_midi_objs := sdlmusic.cpp +endif + + +#### Shadow Warrior + +sw := sw + +sw_root := $(source)/$(sw) +sw_src := $(sw_root)/src +sw_rsrc := $(sw_root)/rsrc +sw_obj := $(obj)/$(sw) + +sw_cflags := -I$(sw_src) + +sw_game_deps := duke3d_common_midi audiolib mact +sw_editor_deps := audiolib + +sw_game := voidsw +sw_editor := voidsw-editor + +sw_game_proper := VoidSW +sw_editor_proper := VoidSW Editor + +sw_game_objs := \ + actor.cpp \ + ai.cpp \ + anim.cpp \ + border.cpp \ + break.cpp \ + bunny.cpp \ + cache.cpp \ + cheats.cpp \ + colormap.cpp \ + common.cpp \ + config.cpp \ + console.cpp \ + coolg.cpp \ + coolie.cpp \ + copysect.cpp \ + demo.cpp \ + draw.cpp \ + eel.cpp \ + game.cpp \ + girlninj.cpp \ + goro.cpp \ + grpscan.cpp \ + hornet.cpp \ + interp.cpp \ + interpsh.cpp \ + inv.cpp \ + jplayer.cpp \ + jsector.cpp \ + jweapon.cpp \ + lava.cpp \ + light.cpp \ + mclip.cpp \ + mdastr.cpp \ + menus.cpp \ + miscactr.cpp \ + morph.cpp \ + net.cpp \ + ninja.cpp \ + panel.cpp \ + player.cpp \ + predict.cpp \ + quake.cpp \ + ripper.cpp \ + ripper2.cpp \ + rooms.cpp \ + rotator.cpp \ + rts.cpp \ + save.cpp \ + scrip2.cpp \ + sector.cpp \ + serp.cpp \ + setup.cpp \ + skel.cpp \ + skull.cpp \ + slidor.cpp \ + sounds.cpp \ + spike.cpp \ + sprite.cpp \ + sumo.cpp \ + swconfig.cpp \ + sync.cpp \ + text.cpp \ + track.cpp \ + vator.cpp \ + vis.cpp \ + wallmove.cpp \ + warp.cpp \ + weapon.cpp \ + zilla.cpp \ + zombie.cpp \ + saveable.cpp \ + +sw_editor_objs := \ + jnstub.cpp \ + brooms.cpp \ + bldscript.cpp \ + jbhlp.cpp \ + colormap.cpp \ + grpscan.cpp \ + common.cpp \ + +sw_game_rsrc_objs := +sw_editor_rsrc_objs := +sw_game_gen_objs := +sw_editor_gen_objs := + +ifeq (1,$(HAVE_GTK2)) + sw_game_objs += startgtk.game.cpp + sw_game_gen_objs += game_banner.c + sw_editor_gen_objs += build_banner.c +endif +ifeq ($(RENDERTYPE),SDL) + sw_game_rsrc_objs += game_icon.c + sw_editor_rsrc_objs += game_icon.c +endif +ifeq ($(PLATFORM),WINDOWS) + sw_game_objs += startwin.game.cpp + sw_game_rsrc_objs += gameres.rc + sw_editor_rsrc_objs += buildres.rc +endif + + +#### Final setup + +COMPILERFLAGS += -I$(engine_inc) -I$(mact_inc) -I$(audiolib_inc) -I$(enet_inc) + + +##### Recipes + +games := \ + kenbuild \ + duke3d \ + sw \ + +libraries := \ + engine \ + audiolib \ + mact \ + enet \ + libxmplite \ + lpeg \ + +components := \ + $(games) \ + $(libraries) \ + tools \ + +roles := \ + game \ + editor \ + + +ifeq ($(PRETTY_OUTPUT),1) +.SILENT: +endif +.PHONY: \ + all \ + start \ + $(addprefix clean,$(games) test utils tools) \ + veryclean \ + clean \ + printutils \ + printtools \ + rev \ + $(engine_obj)/rev.$o \ + clang-tools \ + +.SUFFIXES: +.SECONDEXPANSION: + + +#### Targets + +all: duke3d + +start: + $(BUILD_STARTED) + +tools: $(addsuffix $(EXESUFFIX),$(tools_targets)) | start + @$(call LL,$^) + +$(games): $$(foreach i,$(roles),$$($$@_$$i)$(EXESUFFIX)) | start + @$(call LL,$^) + +ebacktrace: $(ebacktrace_dll) | start + @$(call LL,$^) + +ifeq ($(PLATFORM),WII) +ifneq ($(ELF2DOL),) +%$(DOLSUFFIX): %$(EXESUFFIX) +endif +endif + + +define BUILDRULE + +$$($1_$2)$$(EXESUFFIX): $$(foreach i,$(call getdeps,$1,$2),$$(call expandobjs,$$i)) $$($1_$2_miscdeps) | $$($1_$2_orderonlydeps) + $$(LINK_STATUS) + $$(RECIPE_IF) $$(LINKER) -o $$@ $$^ $$(GUI_LIBS) $$($1_$2_ldflags) $$(LIBDIRS) $$(LIBS) $$(RECIPE_RESULT_LINK) +ifeq ($$(PLATFORM),WII) +ifneq ($$(ELF2DOL),) + $$(ELF2DOL) $$@ $$($1_$2)$$(DOLSUFFIX) +endif +endif +ifneq ($$(STRIP),) + $$(STRIP) $$@ $$($1_$2_stripflags) +endif +ifeq ($$(PLATFORM),DARWIN) + cp -RPf "platform/Apple/bundles/$$($1_$2_proper).app" "./" + $(call MKDIR,"$$($1_$2_proper).app/Contents/MacOS") + cp -f "$$($1_$2)$$(EXESUFFIX)" "$$($1_$2_proper).app/Contents/MacOS/" +endif + +endef + +$(foreach i,$(games),$(foreach j,$(roles),$(eval $(call BUILDRULE,$i,$j)))) + + +include $(lpeg_root)/Dependencies.mak +include $(engine_root)/Dependencies.mak +include $(duke3d_root)/Dependencies.mak +include $(sw_root)/Dependencies.mak + + +#### Rules + +$(ebacktrace_dll): platform/Windows/src/backtrace.c + $(COMPILE_STATUS) + $(RECIPE_IF) $(CC) $(CONLYFLAGS) -O2 -ggdb -shared -Wall -Wextra -static-libgcc -I$(engine_inc) -o $@ $^ -lbfd -liberty -limagehlp $(RECIPE_RESULT_COMPILE) + +libcache1d$(DLLSUFFIX): $(engine_src)/cache1d.cpp + $(COMPILE_STATUS) + $(RECIPE_IF) $(COMPILER_C) -DCACHE1D_COMPRESS_ONLY -shared -fPIC $< -o $@ $(RECIPE_RESULT_COMPILE) + +# to debug the tools link phase, make a copy of this rule explicitly replacing % with the name of a tool, such as kextract +%$(EXESUFFIX): $(tools_obj)/%.$o $(foreach i,tools $(tools_deps),$(call expandobjs,$i)) + $(LINK_STATUS) + $(RECIPE_IF) $(LINKER) -o $@ $^ $(LIBDIRS) $(LIBS) $(RECIPE_RESULT_LINK) + +enumdisplay$(EXESUFFIX): $(tools_obj)/enumdisplay.$o + $(LINK_STATUS) + $(RECIPE_IF) $(LINKER) -o $@ $^ $(LIBDIRS) $(LIBS) -lgdi32 $(RECIPE_RESULT_LINK) +getdxdidf$(EXESUFFIX): $(tools_obj)/getdxdidf.$o + $(LINK_STATUS) + $(RECIPE_IF) $(LINKER) -o $@ $^ $(LIBDIRS) $(LIBS) -ldinput $(RECIPE_RESULT_LINK) + + +### Lunatic + +# Create object files directly with luajit +$(duke3d_obj)/%.$o: $(lunatic_src)/%.lua | $(duke3d_obj) + $(COMPILE_STATUS) + $(RECIPE_IF) $(LUAJIT) -bg $(LUAJIT_BCOPTS) $< $@ $(RECIPE_RESULT_COMPILE) + +$(duke3d_obj)/%.$o: $(lunatic_src)/%.cpp | $(duke3d_obj) + $(COMPILE_STATUS) + $(RECIPE_IF) $(COMPILER_CXX) $(duke3d_cflags) -c $< -o $@ $(RECIPE_RESULT_COMPILE) + +# List of exported symbols, OS X +$(duke3d_obj)/lunatic_%_osx: $(lunatic_src)/%.lds | $(duke3d_obj) + sed 's/[{};]//g;s/[A-Za-z_][A-Za-z_0-9]*/_&/g' $< > $@ + +# List of exported symbols, Windows +$(duke3d_obj)/lunatic_%.def: $(lunatic_src)/%.lds | $(duke3d_obj) + echo EXPORTS > $@ + sed 's/[{};]//g' $< >> $@ + + +### Main Rules + +define OBJECTRULES + +$$($1_obj)/%.$$o: $$($1_src)/%.nasm | $$($1_obj) + $$(COMPILE_STATUS) + $$(RECIPE_IF) $$(AS) $$(ASFLAGS) $$< -o $$@ $$(RECIPE_RESULT_COMPILE) + +$$($1_obj)/%.$$o: $$($1_src)/%.yasm | $$($1_obj) + $$(COMPILE_STATUS) + $$(RECIPE_IF) $$(AS) $$(ASFLAGS) $$< -o $$@ $$(RECIPE_RESULT_COMPILE) + +$$($1_obj)/%.$$o: $$($1_src)/%.c | $$($1_obj) + $$(COMPILE_STATUS) + $$(RECIPE_IF) $$(COMPILER_C) $$($1_cflags) -c $$< -o $$@ $$(RECIPE_RESULT_COMPILE) + +$$($1_obj)/%.$$o: $$($1_src)/%.cpp | $$($1_obj) + $$(COMPILE_STATUS) + $$(RECIPE_IF) $$(COMPILER_CXX) $$($1_cflags) -c $$< -o $$@ $$(RECIPE_RESULT_COMPILE) + +$$($1_obj)/%.$$o: $$($1_src)/%.m | $$($1_obj) + $$(COMPILE_STATUS) + $$(RECIPE_IF) $$(COMPILER_OBJC) $$($1_cflags) -c $$< -o $$@ $$(RECIPE_RESULT_COMPILE) + +$$($1_obj)/%.$$o: $$($1_src)/%.mm | $$($1_obj) + $$(COMPILE_STATUS) + $$(RECIPE_IF) $$(COMPILER_OBJCXX) $$($1_cflags) -c $$< -o $$@ $$(RECIPE_RESULT_COMPILE) + +$$($1_obj)/%.$$o: $$($1_obj)/%.c + $$(COMPILE_STATUS) + $$(RECIPE_IF) $$(COMPILER_C) $$($1_cflags) -c $$< -o $$@ $$(RECIPE_RESULT_COMPILE) + +## Cosmetic stuff + +$$($1_obj)/%.$$o: $$($1_rsrc)/%.rc | $$($1_obj) + $$(COMPILE_STATUS) + $$(RECIPE_IF) $$(RC) -i $$< -o $$@ --include-dir=$$(engine_inc) --include-dir=$$($1_src) --include-dir=$$($1_rsrc) -DPOLYMER=$$(POLYMER) $$(RECIPE_RESULT_COMPILE) + +$$($1_obj)/%.$$o: $$($1_rsrc)/%.c | $$($1_obj) + $$(COMPILE_STATUS) + $$(RECIPE_IF) $$(COMPILER_C) $$($1_cflags) -c $$< -o $$@ $$(RECIPE_RESULT_COMPILE) + +$$($1_obj)/%_banner.c: $$($1_rsrc)/%.bmp | $$($1_obj) + echo "#include \"gtkpixdata_shim.h\"" > $$@ + gdk-pixbuf-csource --extern --struct --raw --name=startbanner_pixdata $$^ | sed 's/load_inc//' >> $$@ + +endef + +$(foreach i,$(components),$(eval $(call OBJECTRULES,$i))) + + +### Other special cases + +# Comment out the following rule to debug a-c.o +$(engine_obj)/a-c.$o: $(engine_src)/a-c.cpp | $(engine_obj) + $(COMPILE_STATUS) + $(RECIPE_IF) $(subst -O$(OPTLEVEL),-O2,$(subst $(ASAN_FLAGS),,$(COMPILER_CXX))) $(engine_cflags) -c $< -o $@ $(RECIPE_RESULT_COMPILE) + +$(engine_obj)/rev.$o: $(engine_src)/rev.cpp | $(engine_obj) + $(COMPILE_STATUS) + $(RECIPE_IF) $(COMPILER_CXX) $(engine_cflags) $(REVFLAG) -c $< -o $@ $(RECIPE_RESULT_COMPILE) + + +### Directories + +$(foreach i,$(components),$($i_obj)): + -$(call MKDIR,$@) + +### Phonies + +clang-tools: $(filter %.c %.cpp,$(foreach i,$(call getdeps,duke3d,game),$(call expandsrcs,$i))) + echo $^ -- -x c++ $(CXXONLYFLAGS) $(COMPILERFLAGS) $(foreach i,$(components),$($i_cflags)) $(CWARNS) + +$(addprefix clean,$(games)): + -$(call RM,$(foreach i,$(roles),$($(subst clean,,$@)_$i)$(EXESUFFIX))) + -$(call RMDIR,$($(subst clean,,$@)_obj)) +ifeq ($(PLATFORM),DARWIN) + -$(call RMDIR,$(foreach i,$(roles),"$($(subst clean,,$@)_$i_proper).app")) +endif + +cleantools: + -$(call RM,$(addsuffix $(EXESUFFIX),$($(subst clean,,$@)_targets))) + -$(call RMDIR,$($(subst clean,,$@)_obj)) + +clean: cleanduke3d cleantools + -$(call RMDIR,$(obj)) + -$(call RM,$(ebacktrace_dll)) + +printtools: + echo "$(addsuffix $(EXESUFFIX),$(tools_targets))" + +rev: $(engine_obj)/rev.$o + + +### Compatibility + +test: kenbuild +utils: tools +printutils: printtools +veryclean: clean +cleanutils: cleantools +cleantest: cleankenbuild diff --git a/patch/config.cpp b/patch/config.cpp new file mode 100644 index 0000000..6081e36 --- /dev/null +++ b/patch/config.cpp @@ -0,0 +1,929 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2016 EDuke32 developers and contributors + +This file is part of EDuke32. + +EDuke32 is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- + +#include "duke3d.h" +#include "scriplib.h" +#include "osdcmds.h" +#include "renderlayer.h" +#include "cmdline.h" + +#ifdef __ANDROID__ +#include "android.h" +#endif + +// we load this in to get default button and key assignments +// as well as setting up function mappings + +#define __SETUP__ // JBF 20031211 +#include "_functio.h" + +hashtable_t h_gamefuncs = { NUMGAMEFUNCTIONS<<1, NULL }; + +int32_t CONFIG_FunctionNameToNum(const char *func) +{ + int32_t i; + + if (!func) + return -1; + + i = hash_find(&h_gamefuncs,func); + + if (i < 0) + { + char *str = Bstrtolower(Xstrdup(func)); + i = hash_find(&h_gamefuncs,str); + Bfree(str); + + return i; + } + + return i; +} + + +char *CONFIG_FunctionNumToName(int32_t func) +{ + if ((unsigned)func >= (unsigned)NUMGAMEFUNCTIONS) + return NULL; + return gamefunctions[func]; +} + + +int32_t CONFIG_AnalogNameToNum(const char *func) +{ + if (!func) + return -1; + + if (!Bstrcasecmp(func,"analog_turning")) + { + return analog_turning; + } + if (!Bstrcasecmp(func,"analog_strafing")) + { + return analog_strafing; + } + if (!Bstrcasecmp(func,"analog_moving")) + { + return analog_moving; + } + if (!Bstrcasecmp(func,"analog_lookingupanddown")) + { + return analog_lookingupanddown; + } + + return -1; +} + + +const char *CONFIG_AnalogNumToName(int32_t func) +{ + switch (func) + { + case analog_turning: + return "analog_turning"; + case analog_strafing: + return "analog_strafing"; + case analog_moving: + return "analog_moving"; + case analog_lookingupanddown: + return "analog_lookingupanddown"; + } + + return NULL; +} + + +void CONFIG_SetDefaultKeys(const char (*keyptr)[MAXGAMEFUNCLEN]) +{ + Bmemset(ud.config.KeyboardKeys, 0xff, sizeof(ud.config.KeyboardKeys)); + + CONTROL_ClearAllBinds(); + + for (size_t i=0; i < ARRAY_SIZE(gamefunctions); ++i) + { + if (gamefunctions[i][0] == '\0') + continue; + + ud.config.KeyboardKeys[i][0] = KB_StringToScanCode(keyptr[i<<1]); + ud.config.KeyboardKeys[i][1] = KB_StringToScanCode(keyptr[(i<<1)+1]); + + if (i == gamefunc_Show_Console) + OSD_CaptureKey(ud.config.KeyboardKeys[i][0]); + else + CONFIG_MapKey(i, ud.config.KeyboardKeys[i][0], 0, ud.config.KeyboardKeys[i][1], 0); + } +} + +void CONFIG_SetDefaults(void) +{ + // JBF 20031211 + int32_t i; + + ud.config.scripthandle = -1; +#ifdef __ANDROID__ + droidinput.forward_sens = 5.f; + droidinput.strafe_sens = 5.f; + droidinput.pitch_sens = 5.f; + droidinput.yaw_sens = 5.f; + droidinput.hideStick = 0; + droidinput.gameControlsAlpha = 0.5; + droidinput.toggleCrouch = 1; + droidinput.quickSelectWeapon = 1; + + ud.config.ScreenWidth = droidinfo.screen_width; + ud.config.ScreenHeight = droidinfo.screen_height; +#else + ud.config.ScreenWidth = 1024; + ud.config.ScreenHeight = 768; +#endif + + ud.config.ScreenMode = 0; + +#ifdef USE_OPENGL + ud.config.ScreenBPP = 32; +#else + ud.config.ScreenBPP = 8; +#endif + ud.config.useprecache = 1; + ud.config.ForceSetup = 1; + ud.config.NoAutoLoad = 1; + ud.config.AmbienceToggle = 1; + ud.config.AutoAim = 1; + ud.config.MasterVolume = 255; + ud.config.FXVolume = 225; +#if defined(_WIN32) + ud.config.MixRate = 44100; +#elif defined __ANDROID__ + ud.config.MixRate = droidinfo.audio_sample_rate; +#else + ud.config.MixRate = 48000; +#endif + ud.config.MouseBias = 0; + ud.config.MouseDeadZone = 0; + ud.config.MusicToggle = 1; + ud.config.MusicVolume = 195; + g_myAimMode = g_player[0].ps->aim_mode = 1; + ud.config.NumBits = 16; + ud.config.NumChannels = 2; +#if defined GEKKO || defined __OPENDINGUX__ + ud.config.NumVoices = 32; +#else + ud.config.NumVoices = 256; +#endif + ud.config.FluidsynthBackend = 0; + ud.config.ReverseStereo = 0; + ud.auto_run = 1; + ud.config.ShowOpponentWeapons = 0; + ud.config.SmoothInput = 1; + ud.config.SoundToggle = 1; + ud.althud = 1; + ud.automsg = 0; + ud.autovote = 0; + ud.brightness = 8; + ud.camerasprite = -1; + +#if defined GEKKO || defined __OPENDINGUX__ + ud.camera_time = 11; +#elif defined(__ANDROID__) + ud.camera_time = 7; +#else + ud.camera_time = 4; +#endif + + ud.color = 0; + ud.crosshair = 1; + ud.crosshairscale = 50; + ud.obituaries = 1; + ud.democams = 1; + ud.detail = 0; + ud.drawweapon = 1; + ud.idplayers = 1; + ud.levelstats = 0; + ud.lockout = 0; + ud.m_ffire = 1; + ud.m_marker = 1; + ud.mouseaiming = 0; + ud.mouseflip = 1; + ud.msgdisptime = 120; + ud.pwlockout[0] = '\0'; + ud.runkey_mode = 0; + ud.screen_size = 4; + ud.screen_tilting = 1; + ud.shadows = 1; + ud.statusbarmode = 1; + ud.statusbarscale = 100; + ud.team = 0; + ud.viewbob = 1; + ud.weaponsway = 1; + ud.weaponswitch = 3; // new+empty + ud.angleinterpolation = 0; +#ifdef GEKKO + ud.config.UseJoystick = 1; +#else + ud.config.UseJoystick = 0; +#endif + ud.config.UseMouse = 1; + ud.config.VoiceToggle = 5; // bitfield, 1 = local, 2 = dummy, 4 = other players in DM + ud.display_bonus_screen = 1; + ud.show_level_text = 1; + ud.configversion = 0; + ud.weaponscale = 100; + ud.textscale = 200; + ud.screenfade = 1; + + ud.config.CheckForUpdates = 1; + + if (g_rtsNamePtr == NULL) + Bstrcpy(ud.rtsname, G_DefaultRtsFile()); + + Bstrcpy(szPlayerName, "Player"); + +#ifndef EDUKE32_STANDALONE + Bstrcpy(ud.ridecule[0], "An inspiration for birth control."); + Bstrcpy(ud.ridecule[1], "You're gonna die for that!"); + Bstrcpy(ud.ridecule[2], "It hurts to be you."); + Bstrcpy(ud.ridecule[3], "Lucky son of a bitch."); + Bstrcpy(ud.ridecule[4], "Hmmm... payback time."); + Bstrcpy(ud.ridecule[5], "You bottom dwelling scum sucker."); + Bstrcpy(ud.ridecule[6], "Damn, you're ugly."); + Bstrcpy(ud.ridecule[7], "Ha ha ha... wasted!"); + Bstrcpy(ud.ridecule[8], "You suck!"); + Bstrcpy(ud.ridecule[9], "AARRRGHHHHH!!!"); +#endif + + // JBF 20031211 + + CONFIG_SetDefaultKeys(keydefaults); + + memset(ud.config.MouseFunctions, -1, sizeof(ud.config.MouseFunctions)); + for (i=0; i<MAXMOUSEBUTTONS; i++) + { + ud.config.MouseFunctions[i][0] = CONFIG_FunctionNameToNum(mousedefaults[i]); + CONTROL_MapButton(ud.config.MouseFunctions[i][0], i, 0, controldevice_mouse); + if (i>=4) continue; + ud.config.MouseFunctions[i][1] = CONFIG_FunctionNameToNum(mouseclickeddefaults[i]); + CONTROL_MapButton(ud.config.MouseFunctions[i][1], i, 1, controldevice_mouse); + } + + memset(ud.config.MouseDigitalFunctions, -1, sizeof(ud.config.MouseDigitalFunctions)); + for (i=0; i<MAXMOUSEAXES; i++) + { + ud.config.MouseAnalogueScale[i] = DEFAULTMOUSEANALOGUESCALE; + CONTROL_SetAnalogAxisScale(i, ud.config.MouseAnalogueScale[i], controldevice_mouse); + + ud.config.MouseDigitalFunctions[i][0] = CONFIG_FunctionNameToNum(mousedigitaldefaults[i*2]); + ud.config.MouseDigitalFunctions[i][1] = CONFIG_FunctionNameToNum(mousedigitaldefaults[i*2+1]); + CONTROL_MapDigitalAxis(i, ud.config.MouseDigitalFunctions[i][0], 0, controldevice_mouse); + CONTROL_MapDigitalAxis(i, ud.config.MouseDigitalFunctions[i][1], 1, controldevice_mouse); + + ud.config.MouseAnalogueAxes[i] = CONFIG_AnalogNameToNum(mouseanalogdefaults[i]); + CONTROL_MapAnalogAxis(i, ud.config.MouseAnalogueAxes[i], controldevice_mouse); + } + CONTROL_MouseSensitivity = DEFAULTMOUSESENSITIVITY; + + memset(ud.config.JoystickFunctions, -1, sizeof(ud.config.JoystickFunctions)); + for (i=0; i<MAXJOYBUTTONSANDHATS; i++) + { + ud.config.JoystickFunctions[i][0] = CONFIG_FunctionNameToNum(joystickdefaults[i]); + ud.config.JoystickFunctions[i][1] = CONFIG_FunctionNameToNum(joystickclickeddefaults[i]); + CONTROL_MapButton(ud.config.JoystickFunctions[i][0], i, 0, controldevice_joystick); + CONTROL_MapButton(ud.config.JoystickFunctions[i][1], i, 1, controldevice_joystick); + } + + memset(ud.config.JoystickDigitalFunctions, -1, sizeof(ud.config.JoystickDigitalFunctions)); + for (i=0; i<MAXJOYAXES; i++) + { + ud.config.JoystickAnalogueScale[i] = DEFAULTJOYSTICKANALOGUESCALE; + ud.config.JoystickAnalogueDead[i] = DEFAULTJOYSTICKANALOGUEDEAD; + ud.config.JoystickAnalogueSaturate[i] = DEFAULTJOYSTICKANALOGUESATURATE; + CONTROL_SetAnalogAxisScale(i, ud.config.JoystickAnalogueScale[i], controldevice_joystick); + + ud.config.JoystickDigitalFunctions[i][0] = CONFIG_FunctionNameToNum(joystickdigitaldefaults[i*2]); + ud.config.JoystickDigitalFunctions[i][1] = CONFIG_FunctionNameToNum(joystickdigitaldefaults[i*2+1]); + CONTROL_MapDigitalAxis(i, ud.config.JoystickDigitalFunctions[i][0], 0, controldevice_joystick); + CONTROL_MapDigitalAxis(i, ud.config.JoystickDigitalFunctions[i][1], 1, controldevice_joystick); + + ud.config.JoystickAnalogueAxes[i] = CONFIG_AnalogNameToNum(joystickanalogdefaults[i]); + CONTROL_MapAnalogAxis(i, ud.config.JoystickAnalogueAxes[i], controldevice_joystick); + } + + VM_OnEvent(EVENT_SETDEFAULTS, g_player[myconnectindex].ps->i, myconnectindex); +} + + +// wrapper for CONTROL_MapKey(), generates key bindings to reflect changes to keyboard setup +void CONFIG_MapKey(int32_t which, kb_scancode key1, kb_scancode oldkey1, kb_scancode key2, kb_scancode oldkey2) +{ + int32_t i, j, k; + int32_t ii[] = { key1, key2, oldkey1, oldkey2 }; + char buf[2*MAXGAMEFUNCLEN]; + + UNREFERENCED_PARAMETER(which); +// CONTROL_MapKey(which, key1, key2); + + if (which == gamefunc_Show_Console) + OSD_CaptureKey(key1); + + for (k = 0; (unsigned)k < ARRAY_SIZE(ii); k++) + { + if (ii[k] == 0xff || !ii[k]) + continue; + + for (j=0; ConsoleKeys[j].name; j++) + if (ii[k] == ConsoleKeys[j].id) + break; + + tempbuf[0] = 0; + + for (i=NUMGAMEFUNCTIONS-1; i>=0; i--) + { + if (ud.config.KeyboardKeys[i][0] == ii[k] || ud.config.KeyboardKeys[i][1] == ii[k]) + { + Bsprintf(buf,"gamefunc_%s; ",CONFIG_FunctionNumToName(i)); + Bstrcat(tempbuf,buf); + } + } + + i = Bstrlen(tempbuf); + if (i >= 2) + { + tempbuf[i-2] = 0; // cut off the trailing "; " + CONTROL_BindKey(ii[k], tempbuf, 1, ConsoleKeys[j].name ? ConsoleKeys[j].name : "<?>"); + } + else + { + CONTROL_FreeKeyBind(ii[k]); + } + } +} + + +void CONFIG_SetupMouse(void) +{ + int32_t i; + char str[80]; + char temp[80]; + int32_t scale; + + if (ud.config.scripthandle < 0) return; + + for (i=0; i<MAXMOUSEBUTTONS; i++) + { + Bsprintf(str,"MouseButton%d",i); + temp[0] = 0; + if (!SCRIPT_GetString(ud.config.scripthandle,"Controls", str,temp)) + ud.config.MouseFunctions[i][0] = CONFIG_FunctionNameToNum(temp); + + Bsprintf(str,"MouseButtonClicked%d",i); + temp[0] = 0; + if (!SCRIPT_GetString(ud.config.scripthandle,"Controls", str,temp)) + ud.config.MouseFunctions[i][1] = CONFIG_FunctionNameToNum(temp); + } + + // map over the axes + for (i=0; i<MAXMOUSEAXES; i++) + { + Bsprintf(str,"MouseAnalogAxes%d",i); + temp[0] = 0; + if (!SCRIPT_GetString(ud.config.scripthandle, "Controls", str,temp)) + if (CONFIG_AnalogNameToNum(temp) != -1 || (!temp[0] && CONFIG_FunctionNameToNum(temp) != -1)) + ud.config.MouseAnalogueAxes[i] = CONFIG_AnalogNameToNum(temp); + + Bsprintf(str,"MouseDigitalAxes%d_0",i); + temp[0] = 0; + if (!SCRIPT_GetString(ud.config.scripthandle, "Controls", str,temp)) + if (CONFIG_FunctionNameToNum(temp) != -1 || (!temp[0] && CONFIG_FunctionNameToNum(temp) != -1)) + ud.config.MouseDigitalFunctions[i][0] = CONFIG_FunctionNameToNum(temp); + + Bsprintf(str,"MouseDigitalAxes%d_1",i); + temp[0] = 0; + if (!SCRIPT_GetString(ud.config.scripthandle, "Controls", str,temp)) + if (CONFIG_FunctionNameToNum(temp) != -1 || (!temp[0] && CONFIG_FunctionNameToNum(temp) != -1)) + ud.config.MouseDigitalFunctions[i][1] = CONFIG_FunctionNameToNum(temp); + + Bsprintf(str,"MouseAnalogScale%d",i); + scale = ud.config.MouseAnalogueScale[i]; + SCRIPT_GetNumber(ud.config.scripthandle, "Controls", str,&scale); + ud.config.MouseAnalogueScale[i] = scale; + } + + { + tempbuf[0] = 0; + SCRIPT_GetString(ud.config.scripthandle, "Controls","Mouse_Sensitivity",&tempbuf[0]); + if (tempbuf[0]) CONTROL_MouseSensitivity = atof(tempbuf); + } + + for (i=0; i<MAXMOUSEBUTTONS; i++) + { + CONTROL_MapButton(ud.config.MouseFunctions[i][0], i, 0, controldevice_mouse); + CONTROL_MapButton(ud.config.MouseFunctions[i][1], i, 1, controldevice_mouse); + } + for (i=0; i<MAXMOUSEAXES; i++) + { + CONTROL_MapAnalogAxis(i, ud.config.MouseAnalogueAxes[i], controldevice_mouse); + CONTROL_MapDigitalAxis(i, ud.config.MouseDigitalFunctions[i][0], 0,controldevice_mouse); + CONTROL_MapDigitalAxis(i, ud.config.MouseDigitalFunctions[i][1], 1,controldevice_mouse); + CONTROL_SetAnalogAxisScale(i, ud.config.MouseAnalogueScale[i], controldevice_mouse); + } +} + + +void CONFIG_SetupJoystick(void) +{ + int32_t i; + char str[80]; + char temp[80]; + int32_t scale; + + if (ud.config.scripthandle < 0) return; + + for (i=0; i<MAXJOYBUTTONSANDHATS; i++) + { + Bsprintf(str,"JoystickButton%d",i); + temp[0] = 0; + if (!SCRIPT_GetString(ud.config.scripthandle,"Controls", str,temp)) + ud.config.JoystickFunctions[i][0] = CONFIG_FunctionNameToNum(temp); + + Bsprintf(str,"JoystickButtonClicked%d",i); + temp[0] = 0; + if (!SCRIPT_GetString(ud.config.scripthandle,"Controls", str,temp)) + ud.config.JoystickFunctions[i][1] = CONFIG_FunctionNameToNum(temp); + } + + // map over the axes + for (i=0; i<MAXJOYAXES; i++) + { + Bsprintf(str,"JoystickAnalogAxes%d",i); + temp[0] = 0; + if (!SCRIPT_GetString(ud.config.scripthandle, "Controls", str,temp)) + if (CONFIG_AnalogNameToNum(temp) != -1 || (!temp[0] && CONFIG_FunctionNameToNum(temp) != -1)) + ud.config.JoystickAnalogueAxes[i] = CONFIG_AnalogNameToNum(temp); + + Bsprintf(str,"JoystickDigitalAxes%d_0",i); + temp[0] = 0; + if (!SCRIPT_GetString(ud.config.scripthandle, "Controls", str,temp)) + if (CONFIG_FunctionNameToNum(temp) != -1 || (!temp[0] && CONFIG_FunctionNameToNum(temp) != -1)) + ud.config.JoystickDigitalFunctions[i][0] = CONFIG_FunctionNameToNum(temp); + + Bsprintf(str,"JoystickDigitalAxes%d_1",i); + temp[0] = 0; + if (!SCRIPT_GetString(ud.config.scripthandle, "Controls", str,temp)) + if (CONFIG_FunctionNameToNum(temp) != -1 || (!temp[0] && CONFIG_FunctionNameToNum(temp) != -1)) + ud.config.JoystickDigitalFunctions[i][1] = CONFIG_FunctionNameToNum(temp); + + Bsprintf(str,"JoystickAnalogScale%d",i); + scale = ud.config.JoystickAnalogueScale[i]; + SCRIPT_GetNumber(ud.config.scripthandle, "Controls", str,&scale); + ud.config.JoystickAnalogueScale[i] = scale; + + Bsprintf(str,"JoystickAnalogDead%d",i); + scale = ud.config.JoystickAnalogueDead[i]; + SCRIPT_GetNumber(ud.config.scripthandle, "Controls", str,&scale); + ud.config.JoystickAnalogueDead[i] = scale; + + Bsprintf(str,"JoystickAnalogSaturate%d",i); + scale = ud.config.JoystickAnalogueSaturate[i]; + SCRIPT_GetNumber(ud.config.scripthandle, "Controls", str,&scale); + ud.config.JoystickAnalogueSaturate[i] = scale; + } + + for (i=0; i<MAXJOYBUTTONSANDHATS; i++) + { + CONTROL_MapButton(ud.config.JoystickFunctions[i][0], i, 0, controldevice_joystick); + CONTROL_MapButton(ud.config.JoystickFunctions[i][1], i, 1, controldevice_joystick); + } + for (i=0; i<MAXJOYAXES; i++) + { + CONTROL_MapAnalogAxis(i, ud.config.JoystickAnalogueAxes[i], controldevice_joystick); + CONTROL_MapDigitalAxis(i, ud.config.JoystickDigitalFunctions[i][0], 0, controldevice_joystick); + CONTROL_MapDigitalAxis(i, ud.config.JoystickDigitalFunctions[i][1], 1, controldevice_joystick); + CONTROL_SetAnalogAxisScale(i, ud.config.JoystickAnalogueScale[i], controldevice_joystick); + } +} + + +int32_t CONFIG_ReadSetup(void) +{ + int32_t dummy; + char commmacro[] = "CommbatMacro# "; + char tempbuf[1024]; + + CONTROL_ClearAssignments(); + CONFIG_SetDefaults(); + + ud.config.setupread = 1; + pathsearchmode = 1; + + if (ud.config.scripthandle < 0) + { + if (SafeFileExists(g_setupFileName)) // JBF 20031211 + ud.config.scripthandle = SCRIPT_Load(g_setupFileName); +#if !defined(EDUKE32_TOUCH_DEVICES) && !defined(EDUKE32_STANDALONE) + else if (SafeFileExists(SETUPFILENAME)) + { + int32_t i; + i=wm_ynbox("Import Configuration Settings", "The configuration file \"%s\" was not found. " + "Import configuration data from \"%s\"?", g_setupFileName, SETUPFILENAME); + if (i) ud.config.scripthandle = SCRIPT_Load(SETUPFILENAME); + } + else if (SafeFileExists("duke3d.cfg")) + { + int32_t i; + i=wm_ynbox("Import Configuration Settings", "The configuration file \"%s\" was not found. " + "Import configuration data from \"duke3d.cfg\"?", g_setupFileName); + if (i) ud.config.scripthandle = SCRIPT_Load("duke3d.cfg"); + } +#endif + } + + pathsearchmode = 0; + + if (ud.config.scripthandle < 0) + return -1; + + for (dummy = 0; dummy < MAXRIDECULE; dummy++) + { + commmacro[13] = dummy+'0'; + SCRIPT_GetString(ud.config.scripthandle, "Comm Setup",commmacro,&ud.ridecule[dummy][0]); + } + + Bmemset(tempbuf, 0, sizeof(tempbuf)); + SCRIPT_GetString(ud.config.scripthandle, "Comm Setup","PlayerName",&tempbuf[0]); + + char nameBuf[64]; + + while (Bstrlen(OSD_StripColors(nameBuf, tempbuf)) > 10) + tempbuf[Bstrlen(tempbuf) - 1] = '\0'; + + Bstrncpyz(szPlayerName, tempbuf, sizeof(szPlayerName)); + + if (g_rtsNamePtr == NULL) + SCRIPT_GetString(ud.config.scripthandle, "Comm Setup","RTSName",&ud.rtsname[0]); + + SCRIPT_GetNumber(ud.config.scripthandle, "Setup", "ConfigVersion", &ud.configversion); + SCRIPT_GetNumber(ud.config.scripthandle, "Setup", "ForceSetup", &ud.config.ForceSetup); + SCRIPT_GetNumber(ud.config.scripthandle, "Setup", "NoAutoLoad", &ud.config.NoAutoLoad); + + if (g_noSetup == 0 && g_modDir[0] == '/') + { + struct Bstat st; + SCRIPT_GetString(ud.config.scripthandle, "Setup","ModDir",&g_modDir[0]); + + if (Bstat(g_modDir, &st)) + { + if ((st.st_mode & S_IFDIR) != S_IFDIR) + { + initprintf("Invalid mod dir in cfg!\n"); + Bsprintf(g_modDir,"/"); + } + } + } + + if (g_grpNamePtr == NULL && g_addonNum == 0) + { + SCRIPT_GetStringPtr(ud.config.scripthandle, "Setup","SelectedGRP",&g_grpNamePtr); + if (g_grpNamePtr && !Bstrlen(g_grpNamePtr)) + g_grpNamePtr = dup_filename(G_DefaultGrpFile()); + } + + if (!NAM_WW2GI) + { + SCRIPT_GetNumber(ud.config.scripthandle, "Screen Setup", "Out",&ud.lockout); + SCRIPT_GetString(ud.config.scripthandle, "Screen Setup","Password",&ud.pwlockout[0]); + } + + SCRIPT_GetNumber(ud.config.scripthandle, "Screen Setup", "ScreenHeight",&ud.config.ScreenHeight); + SCRIPT_GetNumber(ud.config.scripthandle, "Screen Setup", "ScreenMode",&ud.config.ScreenMode); + SCRIPT_GetNumber(ud.config.scripthandle, "Screen Setup", "ScreenWidth",&ud.config.ScreenWidth); + + SCRIPT_GetNumber(ud.config.scripthandle, "Screen Setup", "WindowPositioning", (int32_t *)&windowpos); + + windowx = -1; + windowy = -1; + SCRIPT_GetNumber(ud.config.scripthandle, "Screen Setup", "WindowPosX", (int32_t *)&windowx); + SCRIPT_GetNumber(ud.config.scripthandle, "Screen Setup", "WindowPosY", (int32_t *)&windowy); + + SCRIPT_GetNumber(ud.config.scripthandle, "Screen Setup", "MaxRefreshFreq", (int32_t *)&maxrefreshfreq); + SCRIPT_GetNumber(ud.config.scripthandle, "Screen Setup", "ScreenBPP", &ud.config.ScreenBPP); + + if (ud.config.ScreenBPP < 8) ud.config.ScreenBPP = 32; + +#ifdef POLYMER + SCRIPT_GetNumber(ud.config.scripthandle, "Screen Setup", "Polymer", &dummy); + if (dummy > 0 && ud.config.ScreenBPP >= 16) glrendmode = REND_POLYMER; + else glrendmode = REND_POLYMOST; +#endif + + SCRIPT_GetNumber(ud.config.scripthandle, "Misc", "Executions",&ud.executions); + +#ifdef _WIN32 + SCRIPT_GetNumber(ud.config.scripthandle, "Updates", "CheckForUpdates", &ud.config.CheckForUpdates); + SCRIPT_GetNumber(ud.config.scripthandle, "Updates", "LastUpdateCheck", &ud.config.LastUpdateCheck); +#endif + + ud.config.setupread = 1; + return 0; +} + + +void CONFIG_WriteSettings(void) // save binds and aliases to <cfgname>_settings.cfg +{ + int32_t i; + BFILE *fp; + char *ptr = Xstrdup(g_setupFileName); + char tempbuf[128]; + + if (!Bstrcmp(g_setupFileName, SETUPFILENAME)) + Bsprintf(tempbuf, "settings.cfg"); + else Bsprintf(tempbuf, "%s_settings.cfg", strtok(ptr, ".")); + + fp = Bfopen(tempbuf, "wt"); + + if (fp) + { + Bfprintf(fp,"// this file is automatically generated by %s\n", AppProperName); + Bfprintf(fp,"unbindall\n"); + + for (i=0; i<MAXBOUNDKEYS+MAXMOUSEBUTTONS; i++) + if (CONTROL_KeyIsBound(i)) + Bfprintf(fp,"bind \"%s\"%s \"%s\"\n",CONTROL_KeyBinds[i].key, + CONTROL_KeyBinds[i].repeat?"":" norepeat",CONTROL_KeyBinds[i].cmdstr); + + OSD_WriteAliases(fp); + + if (g_crosshairSum != -1 && g_crosshairSum != DefaultCrosshairColors.r+(DefaultCrosshairColors.g<<8)+(DefaultCrosshairColors.b<<16)) + Bfprintf(fp, "crosshaircolor %d %d %d\n", CrosshairColors.r, CrosshairColors.g, CrosshairColors.b); + + OSD_WriteCvars(fp); + + Bfclose(fp); + + if (!Bstrcmp(g_setupFileName, SETUPFILENAME)) + OSD_Printf("Wrote settings.cfg\n"); + else OSD_Printf("Wrote %s_settings.cfg\n",ptr); + + Bfree(ptr); + return; + } + + if (!Bstrcmp(g_setupFileName, SETUPFILENAME)) + OSD_Printf("Error writing settings.cfg: %s\n", strerror(errno)); + else OSD_Printf("Error writing %s_settings.cfg: %s\n",ptr,strerror(errno)); + + Bfree(ptr); +} + +void CONFIG_WriteSetup(uint32_t flags) +{ + int32_t dummy; + + if (!ud.config.setupread) return; + + if (ud.config.scripthandle < 0) + ud.config.scripthandle = SCRIPT_Init(g_setupFileName); + + SCRIPT_PutNumber(ud.config.scripthandle, "Misc", "Executions",++ud.executions,FALSE,FALSE); + + SCRIPT_PutNumber(ud.config.scripthandle, "Setup","ConfigVersion",BYTEVERSION_EDUKE32,FALSE,FALSE); + SCRIPT_PutNumber(ud.config.scripthandle, "Setup", "ForceSetup",ud.config.ForceSetup,FALSE,FALSE); + SCRIPT_PutNumber(ud.config.scripthandle, "Setup", "NoAutoLoad",ud.config.NoAutoLoad,FALSE,FALSE); + +#ifdef POLYMER + SCRIPT_PutNumber(ud.config.scripthandle, "Screen Setup", "Polymer",glrendmode == REND_POLYMER,FALSE,FALSE); +#endif + + SCRIPT_PutNumber(ud.config.scripthandle, "Screen Setup", "ScreenBPP",ud.config.ScreenBPP,FALSE,FALSE); // JBF 20040523 + SCRIPT_PutNumber(ud.config.scripthandle, "Screen Setup", "ScreenHeight",ud.config.ScreenHeight,FALSE,FALSE); // JBF 20031206 + SCRIPT_PutNumber(ud.config.scripthandle, "Screen Setup", "ScreenMode",ud.config.ScreenMode,FALSE,FALSE); // JBF 20031206 + SCRIPT_PutNumber(ud.config.scripthandle, "Screen Setup", "ScreenWidth",ud.config.ScreenWidth,FALSE,FALSE); // JBF 20031206 + + if (g_grpNamePtr && !g_addonNum) + SCRIPT_PutString(ud.config.scripthandle, "Setup","SelectedGRP",g_grpNamePtr); + +#ifdef STARTUP_SETUP_WINDOW + if (g_noSetup == 0) + SCRIPT_PutString(ud.config.scripthandle, "Setup","ModDir",&g_modDir[0]); +#endif + // exit early after only updating the values that can be changed from the startup window + if (flags & 1) + { + SCRIPT_Save(ud.config.scripthandle, g_setupFileName); + SCRIPT_Free(ud.config.scripthandle); + return; + } + + SCRIPT_PutNumber(ud.config.scripthandle, "Screen Setup", "WindowPositioning", windowpos, FALSE, FALSE); + SCRIPT_PutNumber(ud.config.scripthandle, "Screen Setup", "WindowPosX", windowx, FALSE, FALSE); + SCRIPT_PutNumber(ud.config.scripthandle, "Screen Setup", "WindowPosY", windowy, FALSE, FALSE); + SCRIPT_PutNumber(ud.config.scripthandle, "Screen Setup", "MaxRefreshFreq", maxrefreshfreq, FALSE, FALSE); + + if (!NAM_WW2GI) + { + SCRIPT_PutNumber(ud.config.scripthandle, "Screen Setup", "Out",ud.lockout,FALSE,FALSE); + SCRIPT_PutString(ud.config.scripthandle, "Screen Setup", "Password",ud.pwlockout); + } + +#ifdef _WIN32 + SCRIPT_PutNumber(ud.config.scripthandle, "Updates", "CheckForUpdates", ud.config.CheckForUpdates, FALSE, FALSE); + SCRIPT_PutNumber(ud.config.scripthandle, "Updates", "LastUpdateCheck", ud.config.LastUpdateCheck, FALSE, FALSE); +#endif + + if (ud.config.UseMouse) + { + for (dummy=0; dummy<MAXMOUSEBUTTONS; dummy++) + { + if (CONFIG_FunctionNumToName(ud.config.MouseFunctions[dummy][0])) + { + Bsprintf(buf, "MouseButton%d", dummy); + SCRIPT_PutString(ud.config.scripthandle, "Controls", buf, CONFIG_FunctionNumToName(ud.config.MouseFunctions[dummy][0])); + } + + if (dummy >= (MAXMOUSEBUTTONS-2)) continue; + + if (CONFIG_FunctionNumToName(ud.config.MouseFunctions[dummy][1])) + { + Bsprintf(buf, "MouseButtonClicked%d", dummy); + SCRIPT_PutString(ud.config.scripthandle, "Controls", buf, CONFIG_FunctionNumToName(ud.config.MouseFunctions[dummy][1])); + } + } + + for (dummy=0; dummy<MAXMOUSEAXES; dummy++) + { + if (CONFIG_AnalogNumToName(ud.config.MouseAnalogueAxes[dummy])) + { + Bsprintf(buf, "MouseAnalogAxes%d", dummy); + SCRIPT_PutString(ud.config.scripthandle, "Controls", buf, CONFIG_AnalogNumToName(ud.config.MouseAnalogueAxes[dummy])); + } + + if (CONFIG_FunctionNumToName(ud.config.MouseDigitalFunctions[dummy][0])) + { + Bsprintf(buf, "MouseDigitalAxes%d_0", dummy); + SCRIPT_PutString(ud.config.scripthandle, "Controls", buf, CONFIG_FunctionNumToName(ud.config.MouseDigitalFunctions[dummy][0])); + } + + if (CONFIG_FunctionNumToName(ud.config.MouseDigitalFunctions[dummy][1])) + { + Bsprintf(buf, "MouseDigitalAxes%d_1", dummy); + SCRIPT_PutString(ud.config.scripthandle, "Controls", buf, CONFIG_FunctionNumToName(ud.config.MouseDigitalFunctions[dummy][1])); + } + + if (ud.config.MouseAnalogueScale[dummy] != DEFAULTMOUSEANALOGUESCALE) + { + Bsprintf(buf, "MouseAnalogScale%d", dummy); + SCRIPT_PutNumber(ud.config.scripthandle, "Controls", buf, ud.config.MouseAnalogueScale[dummy], FALSE, FALSE); + } + } + } + + if (ud.config.UseJoystick) + { + for (dummy=0; dummy<MAXJOYBUTTONSANDHATS; dummy++) + { + if (CONFIG_FunctionNumToName(ud.config.JoystickFunctions[dummy][0])) + { + Bsprintf(buf, "JoystickButton%d", dummy); + SCRIPT_PutString(ud.config.scripthandle, "Controls", buf, CONFIG_FunctionNumToName(ud.config.JoystickFunctions[dummy][0])); + } + + if (CONFIG_FunctionNumToName(ud.config.JoystickFunctions[dummy][1])) + { + Bsprintf(buf, "JoystickButtonClicked%d", dummy); + SCRIPT_PutString(ud.config.scripthandle, "Controls", buf, CONFIG_FunctionNumToName(ud.config.JoystickFunctions[dummy][1])); + } + } + for (dummy=0; dummy<MAXJOYAXES; dummy++) + { + if (CONFIG_AnalogNumToName(ud.config.JoystickAnalogueAxes[dummy])) + { + Bsprintf(buf, "JoystickAnalogAxes%d", dummy); + SCRIPT_PutString(ud.config.scripthandle, "Controls", buf, CONFIG_AnalogNumToName(ud.config.JoystickAnalogueAxes[dummy])); + } + + if (CONFIG_FunctionNumToName(ud.config.JoystickDigitalFunctions[dummy][0])) + { + Bsprintf(buf, "JoystickDigitalAxes%d_0", dummy); + SCRIPT_PutString(ud.config.scripthandle, "Controls", buf, CONFIG_FunctionNumToName(ud.config.JoystickDigitalFunctions[dummy][0])); + } + + if (CONFIG_FunctionNumToName(ud.config.JoystickDigitalFunctions[dummy][1])) + { + Bsprintf(buf, "JoystickDigitalAxes%d_1", dummy); + SCRIPT_PutString(ud.config.scripthandle, "Controls", buf, CONFIG_FunctionNumToName(ud.config.JoystickDigitalFunctions[dummy][1])); + } + + if (ud.config.JoystickAnalogueScale[dummy] != DEFAULTJOYSTICKANALOGUESCALE) + { + Bsprintf(buf, "JoystickAnalogScale%d", dummy); + SCRIPT_PutNumber(ud.config.scripthandle, "Controls", buf, ud.config.JoystickAnalogueScale[dummy], FALSE, FALSE); + } + + if (ud.config.JoystickAnalogueDead[dummy] != DEFAULTJOYSTICKANALOGUEDEAD) + { + Bsprintf(buf, "JoystickAnalogDead%d", dummy); + SCRIPT_PutNumber(ud.config.scripthandle, "Controls", buf, ud.config.JoystickAnalogueDead[dummy], FALSE, FALSE); + } + + if (ud.config.JoystickAnalogueSaturate[dummy] != DEFAULTJOYSTICKANALOGUESATURATE) + { + Bsprintf(buf, "JoystickAnalogSaturate%d", dummy); + SCRIPT_PutNumber(ud.config.scripthandle, "Controls", buf, ud.config.JoystickAnalogueSaturate[dummy], FALSE, FALSE); + } + } + } + + SCRIPT_PutString(ud.config.scripthandle, "Comm Setup","PlayerName",&szPlayerName[0]); + + if (g_rtsNamePtr == NULL) + SCRIPT_PutString(ud.config.scripthandle, "Comm Setup","RTSName",&ud.rtsname[0]); + + char commmacro[] = "CommbatMacro# "; + + for (dummy = 0; dummy < MAXRIDECULE; dummy++) + { + commmacro[13] = dummy+'0'; + SCRIPT_PutString(ud.config.scripthandle, "Comm Setup",commmacro,&ud.ridecule[dummy][0]); + } + + SCRIPT_Save(ud.config.scripthandle, g_setupFileName); + + if ((flags & 2) == 0) + SCRIPT_Free(ud.config.scripthandle); + + OSD_Printf("Wrote %s\n",g_setupFileName); + CONFIG_WriteSettings(); + Bfflush(NULL); +} + +static const char *CONFIG_GetMapEntryName(char m[], char const * const mapname) +{ + strcpy(m, mapname); + + char *p = strrchr(m, '/'); + if (!p) p = strrchr(m, '\\'); + if (p) Bmemmove(m, p, Bstrlen(p)+1); + for (p=m; *p; p++) *p = tolower(*p); + + // cheap hack because SCRIPT_GetNumber doesn't like the slashes + p = m; + while (*p == '/') p++; + + return p; +} + +static void CONFIG_GetMD4EntryName(char m[], uint8_t const * const md4) +{ + sprintf(m, "MD4_%08x%08x%08x%08x", + B_BIG32(B_UNBUF32(&md4[0])), B_BIG32(B_UNBUF32(&md4[4])), + B_BIG32(B_UNBUF32(&md4[8])), B_BIG32(B_UNBUF32(&md4[12]))); +} + +int32_t CONFIG_GetMapBestTime(char const * const mapname, uint8_t const * const mapmd4) +{ + if (!ud.config.setupread || ud.config.scripthandle < 0) + return -1; + + char m[37]; + CONFIG_GetMD4EntryName(m, mapmd4); + + int32_t t = -1; + if (SCRIPT_GetNumber(ud.config.scripthandle, "MapTimes", m, &t)) + { + // fall back to map filenames + char m2[BMAX_PATH]; + char const * const p = CONFIG_GetMapEntryName(m2, mapname); + SCRIPT_GetNumber(ud.config.scripthandle, "MapTimes", p, &t); + } + return t; +} + +int32_t CONFIG_SetMapBestTime(uint8_t const * const mapmd4, int32_t const tm) +{ + if (ud.config.scripthandle < 0) ud.config.scripthandle = SCRIPT_Init(g_setupFileName); + if (ud.config.scripthandle < 0) return -1; + + char m[37]; + CONFIG_GetMD4EntryName(m, mapmd4); + + SCRIPT_PutNumber(ud.config.scripthandle, "MapTimes", m, tm, FALSE, FALSE); + return 0; +} + diff --git a/patch/fluidsynth_inc.h b/patch/fluidsynth_inc.h new file mode 100644 index 0000000..957ae9e --- /dev/null +++ b/patch/fluidsynth_inc.h @@ -0,0 +1,6 @@ +#ifndef FLUIDSYNTH_INC_H_ +#define FLUIDSYNTH_INC_H_ + +#include <fluidsynth.h> + +#endif /* FLUIDSYNTH_INC_H_ */ diff --git a/patch/game.h b/patch/game.h new file mode 100644 index 0000000..93d3e22 --- /dev/null +++ b/patch/game.h @@ -0,0 +1,568 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2016 EDuke32 developers and contributors + +This file is part of EDuke32. + +EDuke32 is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- + +#pragma once + +#ifndef game_h_ +#define game_h_ + +#ifndef ONLY_USERDEFS +#include "premap.h" // XXX +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef ONLY_USERDEFS + +// Compile game-side legacy Room over Room code? +#define LEGACY_ROR 1 + +#define USERQUOTE_LEFTOFFSET 5 +#define USERQUOTE_RIGHTOFFSET 14 + +#if defined(GEKKO) || defined(__OPENDINGUX__) +# define VIEWSCREENFACTOR 0 +#elif defined(__ANDROID__) +# define VIEWSCREENFACTOR 1 +#else +# define VIEWSCREENFACTOR 2 +#endif + +enum GametypeFlags_t { + GAMETYPE_COOP = 0x00000001, + GAMETYPE_WEAPSTAY = 0x00000002, + GAMETYPE_FRAGBAR = 0x00000004, + GAMETYPE_SCORESHEET = 0x00000008, + GAMETYPE_DMSWITCHES = 0x00000010, + GAMETYPE_COOPSPAWN = 0x00000020, + GAMETYPE_ACCESSCARDSPRITES = 0x00000040, + GAMETYPE_COOPVIEW = 0x00000080, + GAMETYPE_COOPSOUND = 0x00000100, + GAMETYPE_OTHERPLAYERSINMAP = 0x00000200, + GAMETYPE_ITEMRESPAWN = 0x00000400, + GAMETYPE_MARKEROPTION = 0x00000800, + GAMETYPE_PLAYERSFRIENDLY = 0x00001000, + GAMETYPE_FIXEDRESPAWN = 0x00002000, + GAMETYPE_ACCESSATSTART = 0x00004000, + GAMETYPE_PRESERVEINVENTORYDEATH = 0x00008000, + GAMETYPE_TDM = 0x00010000, + GAMETYPE_TDMSPAWN = 0x00020000 +}; + +// logo control +enum LogoFlags_t { + LOGO_ENABLED = 0x00000001, + LOGO_PLAYANIM = 0x00000002, + LOGO_PLAYMUSIC = 0x00000004, + LOGO_3DRSCREEN = 0x00000008, + LOGO_TITLESCREEN = 0x00000010, + LOGO_DUKENUKEM = 0x00000020, + LOGO_THREEDEE = 0x00000040, + LOGO_PLUTOPAKSPRITE = 0x00000080, + LOGO_SHAREWARESCREENS = 0x00000100, + LOGO_TENSCREEN = 0x00000200, + LOGO_STOPANIMSOUNDS = 0x00000400, + LOGO_NOE4CUTSCENE = 0x00000800, + LOGO_NOE1BONUSSCENE = 0x00001000, + LOGO_NOE2BONUSSCENE = 0x00002000, + LOGO_NOE3BONUSSCENE = 0x00004000, + LOGO_NOE4BONUSSCENE = 0x00008000, + LOGO_NOE1ENDSCREEN = 0x00010000, + LOGO_NOE2ENDSCREEN = 0x00020000, + LOGO_NOE3RADLOGO = 0x00040000, + LOGO_NODUKETEAMTEXT = 0x00080000, + LOGO_NODUKETEAMPIC = 0x00100000, + LOGO_STOPMISCSOUNDS = 0x00200000, + LOGO_NOGAMETITLE = 0x00400000, + LOGO_NOTITLEBAR = 0x00800000, +}; + +void A_DeleteSprite(int spriteNum); + +static inline int32_t G_GetLogoFlags(void) +{ +#if !defined LUNATIC + return Gv_GetVarByLabel("LOGO_FLAGS",255, -1, -1); +#else + extern int32_t g_logoFlags; + return g_logoFlags; +#endif +} + +#ifdef LUNATIC +typedef struct { + vec3_t pos; + int32_t dist, clock; + int16_t ang, horiz, sect; +} camera_t; + +extern camera_t g_camera; + +# define CAMERA(Membname) (g_camera.Membname) +# define CAMERADIST (g_camera.dist) +# define CAMERACLOCK (g_camera.clock) +#else +# define CAMERA(Membname) (ud.camera ## Membname) +# define CAMERADIST g_cameraDistance +# define CAMERACLOCK g_cameraClock +#endif + +#endif + +#define MAXRIDECULE 10 +#define MAXRIDECULELENGTH 40 +#define MAXSAVEGAMENAMESTRUCT 32 +#define MAXSAVEGAMENAME (MAXSAVEGAMENAMESTRUCT-1) +#define MAXPWLOCKOUT 128 +#define MAXRTSNAME 128 + +// KEEPINSYNC lunatic/_defs_game.lua +typedef struct { +#if !defined LUNATIC + vec3_t camerapos; +#endif + int32_t const_visibility,uw_framerate; + int32_t camera_time,folfvel,folavel,folx,foly,fola; + int32_t reccnt,crosshairscale; + + int32_t runkey_mode,statusbarscale,mouseaiming,weaponswitch,drawweapon; // JBF 20031125 + int32_t democams,color,msgdisptime,statusbarmode; + int32_t m_noexits,noexits,autovote,automsg,idplayers; + int32_t team, viewbob, weaponsway, althud, weaponscale, textscale; + int32_t screenarea_x1, screenarea_y1, screenarea_x2, screenarea_y2; + + int32_t entered_name,screen_tilting,shadows,fta_on,executions,auto_run; + int32_t coords,showfps,levelstats,m_coop,coop,screen_size,lockout,crosshair; + int32_t playerai,angleinterpolation,obituaries; + + int32_t respawn_monsters,respawn_items,respawn_inventory,recstat,monsters_off,brightness; + int32_t m_respawn_items,m_respawn_monsters,m_respawn_inventory,m_recstat,m_monsters_off,detail; + int32_t m_ffire,ffire,m_player_skill,m_level_number,m_volume_number,multimode; + int32_t player_skill,level_number,volume_number,m_marker,marker,mouseflip; + int32_t music_episode, music_level; + + vec2_t m_origin; + int32_t playerbest; + + int32_t configversion, bgstretch; +#if !defined LUNATIC + int16_t cameraang, camerasect, camerahoriz; +#endif + int16_t pause_on,from_bonus; + int16_t camerasprite,last_camsprite; + int16_t last_level,secretlevel; + + struct { + int32_t UseJoystick; + int32_t UseMouse; + int32_t AutoAim; + int32_t ShowOpponentWeapons; + int32_t MouseDeadZone,MouseBias; + int32_t SmoothInput; + + // JBF 20031211: Store the input settings because + // (currently) mact can't regurgitate them + int32_t MouseFunctions[MAXMOUSEBUTTONS][2]; + int32_t MouseDigitalFunctions[MAXMOUSEAXES][2]; + int32_t MouseAnalogueAxes[MAXMOUSEAXES]; + int32_t MouseAnalogueScale[MAXMOUSEAXES]; + int32_t JoystickFunctions[MAXJOYBUTTONSANDHATS][2]; + int32_t JoystickDigitalFunctions[MAXJOYAXES][2]; + int32_t JoystickAnalogueAxes[MAXJOYAXES]; + int32_t JoystickAnalogueScale[MAXJOYAXES]; + int32_t JoystickAnalogueDead[MAXJOYAXES]; + int32_t JoystickAnalogueSaturate[MAXJOYAXES]; + uint8_t KeyboardKeys[NUMGAMEFUNCTIONS][2]; + + // + // Sound variables + // + int32_t MasterVolume; + int32_t FXVolume; + int32_t MusicVolume; + int32_t SoundToggle; + int32_t MusicToggle; + int32_t VoiceToggle; + int32_t AmbienceToggle; + + int32_t NumVoices; + int32_t NumChannels; + int32_t NumBits; + int32_t MixRate; + + int32_t ReverseStereo; + + int32_t FluidsynthBackend; + + // + // Screen variables + // + + int32_t ScreenMode; + + int32_t ScreenWidth; + int32_t ScreenHeight; + int32_t ScreenBPP; + + int32_t ForceSetup; + int32_t NoAutoLoad; + + int32_t scripthandle; + int32_t setupread; + + int32_t CheckForUpdates; + int32_t LastUpdateCheck; + int32_t useprecache; + } config; + + char overhead_on,last_overhead,showweapons; + char god,warp_on,cashman,eog,showallmap; + char show_help,scrollmode,noclip; + char ridecule[MAXRIDECULE][MAXRIDECULELENGTH]; + char pwlockout[MAXPWLOCKOUT],rtsname[MAXRTSNAME]; + char display_bonus_screen; + char show_level_text; + char wchoice[MAX_WEAPONS]; + + uint8_t user_map; + uint8_t screenfade; +} user_defs; + +extern user_defs ud; + +#ifndef ONLY_USERDEFS + +// this is checked against http://eduke32.com/VERSION +extern const char *s_buildDate; + +extern const char *g_rtsNamePtr; + +extern char boardfilename[BMAX_PATH], currentboardfilename[BMAX_PATH]; +extern char boardfilename[BMAX_PATH]; + +extern const char *defaultrtsfilename[GAMECOUNT]; +extern const char *G_DefaultRtsFile(void); + +#ifdef LEGACY_ROR +extern char ror_protectedsectors[MAXSECTORS]; +#endif + +extern float r_ambientlight; + +extern int32_t g_Debug; +extern int32_t g_Shareware; +#if !defined LUNATIC +extern int32_t g_cameraClock; +extern int32_t g_cameraDistance; +#endif +extern int32_t g_crosshairSum; +extern int32_t g_doQuickSave; +extern int32_t g_forceWeaponChoice; +extern int32_t g_fakeMultiMode; +extern int32_t g_levelTextTime; +extern int32_t g_quitDeadline; +extern int32_t g_restorePalette; +extern int32_t hud_glowingquotes; +extern int32_t hud_showmapname; +extern int32_t r_maxfps; +extern int32_t tempwallptr; +extern int32_t ticrandomseed; +extern int32_t vote_map; +extern int32_t voting; + +extern int32_t MAXCACHE1DSIZE; +//extern int8_t cheatbuf[MAXCHEATLEN],cheatbuflen; + +#define CROSSHAIR_PAL (MAXPALOOKUPS-RESERVEDPALS-1) + +extern palette_t CrosshairColors; +extern palette_t DefaultCrosshairColors; + +extern uint64_t g_frameDelay; + +int32_t A_CheckInventorySprite(spritetype *s); +int32_t A_InsertSprite(int16_t whatsect, int32_t s_x, int32_t s_y, int32_t s_z, int16_t s_pn, int8_t s_s, uint8_t s_xr, + uint8_t s_yr, int16_t s_a, int16_t s_ve, int16_t s_zv, int16_t s_ow, int16_t s_ss); +int A_Spawn(int j,int pn); +int G_DoMoveThings(void); +//int32_t G_EndOfLevel(void); + +#ifdef YAX_ENABLE +void Yax_SetBunchZs(int32_t sectnum, int32_t cf, int32_t daz); +#else +#define Yax_SetBunchZs(sectnum, cf, daz) +#endif + +#ifdef LUNATIC +void El_CreateGameState(void); +#endif +void G_PostCreateGameState(void); + +void A_SpawnCeilingGlass(int spriteNum,int sectNum,int glassCnt); +void A_SpawnGlass(int spriteNum,int glassCnt); +void A_SpawnRandomGlass(int spriteNum,int wallNum,int glassCnt); +void A_SpawnWallGlass(int i,int wallnum,int n); +void G_AddUserQuote(const char *daquote); +void G_BackToMenu(void); +void G_DumpDebugInfo(void); + +const char* G_PrintYourTime(void); +const char* G_PrintParTime(void); +const char* G_PrintDesignerTime(void); +const char* G_PrintBestTime(void); +void G_BonusScreen(int32_t bonusonly); +//void G_CheatGetInv(void); +void G_DisplayRest(int32_t smoothratio); +void G_DoSpriteAnimations(int32_t ourx, int32_t oury, int32_t oura, int32_t smoothratio); +void G_DrawBackground(void); +void G_DrawFrags(void); +void G_HandleMirror(int32_t x, int32_t y, int32_t z, int32_t a, int32_t horiz, int32_t smoothratio); +void G_DrawRooms(int32_t snum,int32_t smoothratio); +void G_DrawTXDigiNumZ(int32_t starttile,int32_t x,int32_t y,int32_t n,int32_t s,int32_t pal,int32_t cs,int32_t x1,int32_t y1,int32_t x2,int32_t y2,int32_t z); +int G_FPSLimit(void); +void G_GameExit(const char *t) ATTRIBUTE((noreturn)); +void G_GameQuit(void); +void G_GetCrosshairColor(void); +void G_HandleLocalKeys(void); +void G_HandleSpecialKeys(void); +void G_UpdateAppTitle(void); +void G_PrintGameQuotes(int32_t snum); +//void G_SE40(int32_t smoothratio); +void G_SetCrosshairColor(int32_t r,int32_t g,int32_t b); +void G_Shutdown(void); +void G_UpdatePlayerFromMenu(void); +void M32RunScript(const char *s); +void P_DoQuote(int32_t q,DukePlayer_t *p); +void P_SetGamePalette(DukePlayer_t *player, uint32_t palid, int32_t set); + +#define NEG_ALPHA_TO_BLEND(alpha, blend, orientation) do { \ + if (alpha < 0) { blend = -alpha; alpha = 0; orientation |= RS_TRANS1; } \ +} while (0) + +// Cstat protection mask for (currently) spawned MASKWALL* sprites. +// TODO: look at more cases of cstat=(cstat&PROTECTED)|ADDED in A_Spawn()? +// 2048+(32+16)+8+4 +#define SPAWN_PROTECT_CSTAT_MASK (CSTAT_SPRITE_NOSHADE|CSTAT_SPRITE_SLAB|CSTAT_SPRITE_XFLIP|CSTAT_SPRITE_YFLIP); + +void fadepal(int32_t r,int32_t g,int32_t b,int32_t start,int32_t end,int32_t step); +//void fadepaltile(int32_t r,int32_t g,int32_t b,int32_t start,int32_t end,int32_t step,int32_t tile); +void G_InitTimer(int32_t ticpersec); + +static inline int32_t G_GetTeamPalette(int32_t team) +{ + int8_t pal[] = { 3, 10, 11, 12 }; + + if ((unsigned)team >= ARRAY_SIZE(pal)) + return 0; + + return pal[team]; +} + +#define A_CheckSpriteFlags(spriteNum, iType) (((g_tile[sprite[spriteNum].picnum].flags^actor[spriteNum].flags) & iType) != 0) +// (unsigned)iPicnum check: AMC TC Rusty Nails, bayonet MG alt. fire, iPicnum == -1 (via aplWeaponShoots) +#define A_CheckSpriteTileFlags(iPicnum, iType) (((unsigned)iPicnum < MAXTILES) && (g_tile[iPicnum].flags & iType) != 0) +#define S_StopSound(num) S_StopEnvSound(num, -1) + +extern int G_StartRTS(int lumpNum, int localPlayer); + +extern void G_MaybeAllocPlayer(int32_t pnum); + +static inline void G_HandleAsync(void) +{ + handleevents(); + Net_GetPackets(); +} + +static inline int32_t calc_smoothratio(int32_t totalclk, int32_t ototalclk) +{ + return clamp((totalclk-ototalclk)*(65536/TICSPERFRAME), 0, 65536); +} + +// sector effector lotags +enum +{ + SE_0_ROTATING_SECTOR = 0, + SE_1_PIVOT = 1, + SE_2_EARTHQUAKE = 2, + SE_3_RANDOM_LIGHTS_AFTER_SHOT_OUT = 3, + SE_4_RANDOM_LIGHTS = 4, + SE_5 = 5, + SE_6_SUBWAY = 6, + // ^^ potentially incomplete substitution in code + // vv almost surely complete substitution + SE_7_TELEPORT = 7, + SE_8_UP_OPEN_DOOR_LIGHTS = 8, + SE_9_DOWN_OPEN_DOOR_LIGHTS = 9, + SE_10_DOOR_AUTO_CLOSE = 10, + SE_11_SWINGING_DOOR = 11, + SE_12_LIGHT_SWITCH = 12, + SE_13_EXPLOSIVE = 13, + SE_14_SUBWAY_CAR = 14, + SE_15_SLIDING_DOOR = 15, + SE_16_REACTOR = 16, + SE_17_WARP_ELEVATOR = 17, + SE_18_INCREMENTAL_SECTOR_RISE_FALL = 18, + SE_19_EXPLOSION_LOWERS_CEILING = 19, + SE_20_STRETCH_BRIDGE = 20, + SE_21_DROP_FLOOR = 21, + SE_22_TEETH_DOOR = 22, + SE_23_ONE_WAY_TELEPORT = 23, + SE_24_CONVEYOR = 24, + SE_25_PISTON = 25, + SE_26 = 26, + SE_27_DEMO_CAM = 27, + SE_28_LIGHTNING = 28, + SE_29_WAVES = 29, + SE_30_TWO_WAY_TRAIN = 30, + SE_31_FLOOR_RISE_FALL = 31, + SE_32_CEILING_RISE_FALL = 32, + SE_33_QUAKE_DEBRIS = 33, + SE_34 = 34, // XXX + SE_35 = 35, // XXX + SE_36_PROJ_SHOOTER = 36, + SE_49_POINT_LIGHT = 49, + SE_50_SPOT_LIGHT = 50, + SE_130 = 130, + SE_131 = 131, +}; + +// sector lotags +enum +{ + ST_0_NO_EFFECT = 0, + ST_1_ABOVE_WATER = 1, + ST_2_UNDERWATER = 2, + ST_3 = 3, + // ^^^ maybe not complete substitution in code + ST_9_SLIDING_ST_DOOR = 9, + ST_15_WARP_ELEVATOR = 15, + ST_16_PLATFORM_DOWN = 16, + ST_17_PLATFORM_UP = 17, + ST_18_ELEVATOR_DOWN = 18, + ST_19_ELEVATOR_UP = 19, + ST_20_CEILING_DOOR = 20, + ST_21_FLOOR_DOOR = 21, + ST_22_SPLITTING_DOOR = 22, + ST_23_SWINGING_DOOR = 23, + ST_25_SLIDING_DOOR = 25, + ST_26_SPLITTING_ST_DOOR = 26, + ST_27_STRETCH_BRIDGE = 27, + ST_28_DROP_FLOOR = 28, + ST_29_TEETH_DOOR = 29, + ST_30_ROTATE_RISE_BRIDGE = 30, + ST_31_TWO_WAY_TRAIN = 31, + // left: ST 32767, 65534, 65535 +}; + + +#define G_ModDirSnprintf(buf, size, basename, ...) \ + \ +(((g_modDir[0] != '/') ? Bsnprintf(buf, size, "%s/" basename, g_modDir, ##__VA_ARGS__) \ + : Bsnprintf(buf, size, basename, ##__VA_ARGS__)) >= ((int32_t)size) - 1\ +) +#define G_ModDirSnprintfLite(buf, size, basename) \ + \ +((g_modDir[0] != '/') ? Bsnprintf(buf, size, "%s/%s", g_modDir, basename) \ + : Bsnprintf(buf, size, basename)) + +static inline void G_NewGame_EnterLevel(void) +{ + G_NewGame(ud.m_volume_number, ud.m_level_number, ud.m_player_skill); + + if (G_EnterLevel(MODE_GAME)) + G_BackToMenu(); +} + +static inline int G_GetMusicIdx(const char *str) +{ + int32_t lev, ep; + char b1, b2; + + int numMatches = sscanf(str, "%c%d%c%d", &b1,&ep, &b2,&lev); + + if (numMatches != 4 || Btoupper(b1) != 'E' || Btoupper(b2) != 'L') + return -1; + + if ((unsigned)--lev >= MAXLEVELS || (unsigned)--ep >= MAXVOLUMES) + return -2; + + return (ep * MAXLEVELS) + lev; +} + +static inline int G_GetViewscreenSizeShift(const uspritetype *tspr) +{ +#if VIEWSCREENFACTOR == 0 + UNREFERENCED_PARAMETER(tspr); + return VIEWSCREENFACTOR; +#else + static const int mask = (1<<VIEWSCREENFACTOR)-1; + const int rem = (tspr->xrepeat & mask) | (tspr->yrepeat & mask); + + for (bssize_t i=0; i < VIEWSCREENFACTOR; i++) + if (rem & (1<<i)) + return i; + + return VIEWSCREENFACTOR; +#endif +} + +extern void G_StartMusic(void); + +#ifdef LUNATIC +void El_SetCON(const char *conluacode); +#endif + +EXTERN_INLINE_HEADER void G_SetStatusBarScale(int32_t sc); + +EXTERN_INLINE_HEADER void SetIfGreater(int32_t *variable, int32_t potentialValue); + +#endif + +#ifdef __cplusplus +} +#endif + +#ifndef ONLY_USERDEFS + +#if defined game_c_ || !defined DISABLE_INLINING + +EXTERN_INLINE void G_SetStatusBarScale(int32_t sc) +{ + ud.statusbarscale = clamp(sc, 36, 100); + G_UpdateScreenArea(); +} + +// the point of this is to prevent re-running a function or calculation passed to potentialValue +// without making a new variable under each individual circumstance +EXTERN_INLINE void SetIfGreater(int32_t *variable, int32_t potentialValue) +{ + if (potentialValue > *variable) + *variable = potentialValue; +} + +#endif + +#endif + +#endif diff --git a/patch/menus.cpp b/patch/menus.cpp new file mode 100644 index 0000000..e13ee57 --- /dev/null +++ b/patch/menus.cpp @@ -0,0 +1,6639 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2016 EDuke32 developers and contributors + +This file is part of EDuke32. + +EDuke32 is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- + +#include "compat.h" +#include "duke3d.h" +#include "osdcmds.h" +#include "savegame.h" +#include "demo.h" +#include "xxhash.h" +#include "input.h" +#include "menus.h" +#include "cheats.h" + +#include "in_android.h" +#ifndef __ANDROID__ +droidinput_t droidinput; +#endif + +// common positions +#define MENU_MARGIN_REGULAR 40 +#define MENU_MARGIN_WIDE 32 +#define MENU_MARGIN_CENTER 160 +#define MENU_HEIGHT_CENTER 100 + +int32_t g_skillSoundVoice = -1; + +extern int32_t voting; + +#define USERMAPENTRYLENGTH 25 + +static FORCE_INLINE void WithSDL2_StartTextInput() +{ +#if defined EDUKE32_TOUCH_DEVICES && defined SDL_MAJOR_VERSION && SDL_MAJOR_VERSION > 1 +# if defined __ANDROID__ + AndroidShowKeyboard(1); +# else + SDL_StartTextInput(); +# endif +#endif +} + +static FORCE_INLINE void WithSDL2_StopTextInput() +{ +#if defined EDUKE32_TOUCH_DEVICES && defined SDL_MAJOR_VERSION && SDL_MAJOR_VERSION > 1 +# if defined __ANDROID__ + AndroidShowKeyboard(0); +# else + SDL_StopTextInput(); +# endif +#endif +} + +static FORCE_INLINE void rotatesprite_ybounds(int32_t sx, int32_t sy, int32_t z, int16_t a, int16_t picnum, int8_t dashade, char dapalnum, int32_t dastat, int32_t ydim_upper, int32_t ydim_lower) +{ + rotatesprite_(sx, sy, z, a, picnum, dashade, dapalnum, dastat, 0, 0, 0, ydim_upper, xdim-1, ydim_lower); +} + +#ifndef EDUKE32_STANDALONE +static void mgametext(int32_t x, int32_t y, char const * t) +{ + G_ScreenText(MF_Bluefont.tilenum, x, y, MF_Bluefont.zoom, 0, 0, t, 0, MF_Bluefont.pal, 2|8|16|ROTATESPRITE_FULL16, 0, MF_Bluefont.emptychar.x, MF_Bluefont.emptychar.y, MF_Bluefont.between.x, MF_Bluefont.between.y, MF_Bluefont.textflags, 0, 0, xdim-1, ydim-1); +} +#endif +static vec2_t mgametextcenter(int32_t x, int32_t y, char const * t, int32_t f = 0) +{ + return G_ScreenText(MF_Bluefont.tilenum, (MENU_MARGIN_CENTER<<16) + x, y, MF_Bluefont.zoom, 0, 0, t, 0, MF_Bluefont.pal, 2|8|16|ROTATESPRITE_FULL16, 0, MF_Bluefont.emptychar.x, MF_Bluefont.emptychar.y, MF_Bluefont.between.x, MF_Bluefont.between.y, MF_Bluefont.textflags|f|TEXT_XCENTER, 0, 0, xdim-1, ydim-1); +} + +#define mminitext(x,y,t,p) minitext_(x, y, t, 0, p, 2|8|16|ROTATESPRITE_FULL16) +#define mmenutext menutext + +#ifndef EDUKE32_STANDALONE +static void shadowminitext(int32_t x, int32_t y, const char *t, int32_t p) +{ + int32_t f = 0; + + if (!minitext_lowercase) + f |= TEXT_UPPERCASE; + + G_ScreenTextShadow(1, 1, MINIFONT, x, y, 65536, 0, 0, t, 0, p, 2|8|16|ROTATESPRITE_FULL16, 0, 4<<16, 8<<16, 1<<16, 0, f, 0, 0, xdim-1, ydim-1); +} +#endif +static void creditsminitext(int32_t x, int32_t y, const char *t, int32_t p) +{ + int32_t f = TEXT_XCENTER; + + if (!minitext_lowercase) + f |= TEXT_UPPERCASE; + + G_ScreenTextShadow(1, 1, MINIFONT, x, y, 65536, 0, 0, t, 0, p, 2|8|16|ROTATESPRITE_FULL16, 0, 4<<16, 8<<16, 1<<16, 0, f, 0, 0, xdim-1, ydim-1); +} + +#pragma pack(push,1) +static savehead_t savehead; +#pragma pack(pop) + +static void Menu_DrawBackground(const vec2_t origin) +{ + rotatesprite_fs(origin.x + (MENU_MARGIN_CENTER<<16), origin.y + (100<<16), 65536L,0,MENUSCREEN,16,0,10+64); +} + +static void Menu_DrawTopBar(const vec2_t origin) +{ + if ((G_GetLogoFlags() & LOGO_NOTITLEBAR) == 0) + rotatesprite_fs(origin.x + (MENU_MARGIN_CENTER<<16), origin.y + (19<<16), MF_Redfont.cursorScale, 0,MENUBAR,16,0,10); +} + +static void Menu_DrawTopBarCaption(const char *caption, const vec2_t origin) +{ + char *s = Bstrdup(caption), p = Bstrlen(caption)-1; + if (s[p] == ':') s[p] = 0; + menutext_(origin.x + (MENU_MARGIN_CENTER<<16), origin.y + (24<<16) + (15<<15), 0, s, 10|16, TEXT_XCENTER|TEXT_YCENTER); + Bfree(s); +} + +static FORCE_INLINE int32_t Menu_CursorShade(void) +{ + return 4-(sintable[(totalclock<<4)&2047]>>11); +} +static void Menu_DrawCursorCommon(int32_t x, int32_t y, int32_t z, int32_t picnum, int32_t ydim_upper = 0, int32_t ydim_lower = ydim-1) +{ + rotatesprite_(x, y, z, 0, picnum, Menu_CursorShade(), 0, 2|8, 0, 0, 0, ydim_upper, xdim-1, ydim_lower); +} +static void Menu_DrawCursorLeft(int32_t x, int32_t y, int32_t z) +{ + if (KXDWN) return; + Menu_DrawCursorCommon(x, y, z, SPINNINGNUKEICON+((totalclock>>3)%7)); +} +static void Menu_DrawCursorRight(int32_t x, int32_t y, int32_t z) +{ + if (KXDWN) return; + Menu_DrawCursorCommon(x, y, z, SPINNINGNUKEICON+6-((6+(totalclock>>3))%7)); +} +static void Menu_DrawCursorTextTile(int32_t x, int32_t y, int32_t h, int32_t picnum, vec2s_t const & siz, int32_t ydim_upper = 0, int32_t ydim_lower = ydim-1) +{ + vec2_t const adjsiz = { siz.x<<15, siz.y<<16 }; + Menu_DrawCursorCommon(x + scale(adjsiz.x, h, adjsiz.y), y, divscale16(h, adjsiz.y), picnum, ydim_upper, ydim_lower); +} +static void Menu_DrawCursorText(int32_t x, int32_t y, int32_t h, int32_t ydim_upper = 0, int32_t ydim_lower = ydim-1) +{ + vec2s_t const & siz = tilesiz[SPINNINGNUKEICON]; + + if (KXDWN || siz.x == 0) + { + Menu_DrawCursorTextTile(x, y, h, SMALLFNTCURSOR, tilesiz[SMALLFNTCURSOR], ydim_upper, ydim_lower); + return; + } + + Menu_DrawCursorTextTile(x, y, h, SPINNINGNUKEICON+((totalclock>>3)%7), siz, ydim_upper, ydim_lower); +} + +extern int32_t g_quitDeadline; + + + + + + + + + + + + + +/* +All MAKE_* macros are generally for the purpose of keeping state initialization +separate from actual data. Alternatively, they can serve to factor out repetitive +stuff and keep the important bits from getting lost to our eyes. + +They serve as a stand-in for C++ default value constructors, since we're using C89. + +Note that I prefer to include a space on the inside of the macro parentheses, since +they effectively stand in for curly braces as struct initializers. +*/ + + +// common font types +// tilenums are set after namesdyn runs +MenuFont_t MF_Redfont = { { 5<<16, 15<<16 }, { 0, 0 }, 65536, 20<<16, 110<<16, 65536, TEXT_BIGALPHANUM | TEXT_UPPERCASE, -1, 10, 0, 1 }; +MenuFont_t MF_Bluefont = { { 5<<16, 7<<16 }, { -(1<<16), 2<<16 }, 65536, 10<<16, 110<<16, 32768, 0, -1, 10, 0, 16 }; +MenuFont_t MF_BluefontRed = { { 5<<16, 7<<16 }, { -(1<<16), 2<<16 }, 65536, 10<<16, 110<<16, 32768, 0, -1, 10, 10, 16 }; +MenuFont_t MF_BluefontGame = { { 5<<16, 7<<16 }, { 0, 0 }, 65536, 10<<16, 110<<16, 32768, 0, -1, 10, 0, 16 }; +static MenuFont_t MF_Minifont = { { 4<<16, 5<<16 }, { 1<<16, 1<<16 }, 65536, 10<<16, 110<<16, 32768, 0, -1, 10, 0, 16 }; +static MenuFont_t MF_MinifontRed = { { 4<<16, 5<<16 }, { 1<<16, 1<<16 }, 65536, 10<<16, 110<<16, 32768, 0, -1, 16, 21, 16 }; +MenuFont_t MF_MinifontSave = { { 4<<16, 5<<16 }, { 1<<16, 1<<16 }, 65536, 10<<16, 110<<16, 32768, 0, -1, 10, 0, 13 }; + + +static MenuMenuFormat_t MMF_Top_Main = { { MENU_MARGIN_CENTER<<16, 55<<16, }, -(170<<16) }; +static MenuMenuFormat_t MMF_Top_Episode = { { MENU_MARGIN_CENTER<<16, 48<<16, }, -(190<<16) }; +static MenuMenuFormat_t MMF_Top_Skill = { { MENU_MARGIN_CENTER<<16, 58<<16, }, -(190<<16) }; +static MenuMenuFormat_t MMF_Top_Options = { { MENU_MARGIN_CENTER<<16, 38<<16, }, -(190<<16) }; +static MenuMenuFormat_t MMF_Top_Joystick_Network = { { MENU_MARGIN_CENTER<<16, 70<<16, }, -(190<<16) }; +static MenuMenuFormat_t MMF_BigOptions = { { MENU_MARGIN_WIDE<<16, 38<<16, }, -(190<<16) }; +static MenuMenuFormat_t MMF_SmallOptions = { { MENU_MARGIN_WIDE<<16, 37<<16, }, 170<<16 }; +static MenuMenuFormat_t MMF_Macros = { { 26<<16, 40<<16, }, 160<<16 }; +static MenuMenuFormat_t MMF_SmallOptionsNarrow = { { MENU_MARGIN_REGULAR<<16, 38<<16, }, -(190<<16) }; +static MenuMenuFormat_t MMF_KeyboardSetupFuncs = { { 70<<16, 34<<16, }, 151<<16 }; +static MenuMenuFormat_t MMF_MouseJoySetupBtns = { { 76<<16, 34<<16, }, 143<<16 }; +static MenuMenuFormat_t MMF_FuncList = { { 100<<16, 51<<16, }, 152<<16 }; +static MenuMenuFormat_t MMF_ColorCorrect = { { MENU_MARGIN_REGULAR<<16, 86<<16, }, 190<<16 }; +static MenuMenuFormat_t MMF_BigSliders = { { MENU_MARGIN_WIDE<<16, 37<<16, }, 190<<16 }; +static MenuMenuFormat_t MMF_LoadSave = { { 200<<16, 49<<16, }, 145<<16 }; +static MenuMenuFormat_t MMF_NetSetup = { { 36<<16, 38<<16, }, 190<<16 }; +static MenuMenuFormat_t MMF_FileSelectLeft = { { 40<<16, 45<<16, }, 162<<16 }; +static MenuMenuFormat_t MMF_FileSelectRight = { { 164<<16, 45<<16, }, 162<<16 }; + +static MenuEntryFormat_t MEF_Null = { 0, 0, 0 }; +static MenuEntryFormat_t MEF_MainMenu = { 4<<16, 0, 0 }; +static MenuEntryFormat_t MEF_OptionsMenu = { 7<<16, 0, 0 }; +static MenuEntryFormat_t MEF_CenterMenu = { 7<<16, 0, 0 }; +static MenuEntryFormat_t MEF_BigOptions_Apply = { 4<<16, 16<<16, -(244<<16) }; +static MenuEntryFormat_t MEF_BigOptionsRt = { 4<<16, 0, -(260<<16) }; +#if defined USE_OPENGL || !defined EDUKE32_ANDROID_MENU +static MenuEntryFormat_t MEF_SmallOptions = { 1<<16, 0, 216<<16 }; +#endif +static MenuEntryFormat_t MEF_BigCheats = { 3<<16, 0, -(260<<16) }; +static MenuEntryFormat_t MEF_Cheats = { 2<<16, 0, -(260<<16) }; +static MenuEntryFormat_t MEF_PlayerNarrow = { 1<<16, 0, 90<<16 }; +static MenuEntryFormat_t MEF_Macros = { 2<<16, -1, 268<<16 }; +static MenuEntryFormat_t MEF_VideoSetup = { 4<<16, 0, 168<<16 }; +static MenuEntryFormat_t MEF_VideoSetup_Apply = { 4<<16, 16<<16, 168<<16 }; +static MenuEntryFormat_t MEF_FuncList = { 3<<16, 0, 100<<16 }; +static MenuEntryFormat_t MEF_ColorCorrect = { 2<<16, 0, -(240<<16) }; +static MenuEntryFormat_t MEF_BigSliders = { 2<<16, 0, 170<<16 }; +static MenuEntryFormat_t MEF_LoadSave = { 2<<16, -1, 78<<16 }; +static MenuEntryFormat_t MEF_NetSetup = { 4<<16, 0, 112<<16 }; +static MenuEntryFormat_t MEF_NetSetup_Confirm = { 4<<16, 16<<16, 112<<16 }; + +// common menu option sets +#define MAKE_MENUOPTIONSET(optionNames, optionValues, features) { optionNames, optionValues, &MMF_FuncList, &MEF_FuncList, &MF_Minifont, ARRAY_SIZE(optionNames), -1, 0, features } +#define MAKE_MENUOPTIONSETDYN(optionNames, optionValues, numOptions, features) { optionNames, optionValues, &MMF_FuncList, &MEF_FuncList, &MF_Minifont, numOptions, -1, 0, features } +#define MAKE_MENUOPTIONSETNULL { NULL, NULL, &MMF_FuncList, &MEF_FuncList, &MF_Minifont, 0, -1, 0, 0 } + +static char const *MEOSN_OffOn[] = { "Off", "On", }; +static MenuOptionSet_t MEOS_OffOn = MAKE_MENUOPTIONSET( MEOSN_OffOn, NULL, 0x3 ); +static char const *MEOSN_OnOff[] = { "On", "Off", }; +static MenuOptionSet_t MEOS_OnOff = MAKE_MENUOPTIONSET( MEOSN_OnOff, NULL, 0x3 ); +static char const *MEOSN_NoYes[] = { "No", "Yes", }; +static MenuOptionSet_t MEOS_NoYes = MAKE_MENUOPTIONSET( MEOSN_NoYes, NULL, 0x3 ); +static char const *MEOSN_YesNo[] = { "Yes", "No", }; +static MenuOptionSet_t MEOS_YesNo = MAKE_MENUOPTIONSET( MEOSN_YesNo, NULL, 0x3 ); + + +static char MenuGameFuncs[NUMGAMEFUNCTIONS][MAXGAMEFUNCLEN]; +static char const *MenuGameFuncNone = " -None-"; +static char const *MEOSN_Gamefuncs[NUMGAMEFUNCTIONS+1]; +static int32_t MEOSV_Gamefuncs[NUMGAMEFUNCTIONS+1]; +static MenuOptionSet_t MEOS_Gamefuncs = MAKE_MENUOPTIONSET( MEOSN_Gamefuncs, MEOSV_Gamefuncs, 0x1 ); + + + +/* +MenuEntry_t is passed in arrays of pointers so that the callback function +that is called when an entry is modified or activated can test equality of the current +entry pointer directly against the known ones, instead of relying on an ID number. + +That way, individual menu entries can be ifdef'd out painlessly. +*/ + +static MenuLink_t MEO_NULL = { MENU_NULL, MA_None, }; +static const char* MenuCustom = "Custom"; + +#define MAKE_MENUSTRING(...) { NULL, __VA_ARGS__, } +#define MAKE_MENUOPTION(...) { __VA_ARGS__, -1, } +#define MAKE_MENURANGE(...) { __VA_ARGS__, } +#define MAKE_MENUENTRY(...) { __VA_ARGS__, 0, 0, 0, } + + +#define MAKE_SPACER( EntryName, Height ) \ +static MenuSpacer_t MEO_ ## EntryName = { Height }; + +MAKE_SPACER( Space2, 2<<16 ); // bigoptions +MAKE_SPACER( Space4, 4<<16 ); // usermap, smalloptions, anything else non-top +MAKE_SPACER( Space6, 6<<16 ); // videosetup +MAKE_SPACER( Space8, 8<<16 ); // colcorr, redslide + +static MenuEntry_t ME_Space2_Redfont = MAKE_MENUENTRY( NULL, &MF_Redfont, &MEF_Null, &MEO_Space2, Spacer ); +static MenuEntry_t ME_Space4_Bluefont = MAKE_MENUENTRY( NULL, &MF_Bluefont, &MEF_Null, &MEO_Space4, Spacer ); +#ifndef EDUKE32_SIMPLE_MENU +static MenuEntry_t ME_Space4_Redfont = MAKE_MENUENTRY( NULL, &MF_Redfont, &MEF_Null, &MEO_Space4, Spacer ); +static MenuEntry_t ME_Space8_Bluefont = MAKE_MENUENTRY( NULL, &MF_Bluefont, &MEF_Null, &MEO_Space8, Spacer ); +#endif +static MenuEntry_t ME_Space6_Redfont = MAKE_MENUENTRY( NULL, &MF_Redfont, &MEF_Null, &MEO_Space6, Spacer ); +static MenuEntry_t ME_Space8_Redfont = MAKE_MENUENTRY( NULL, &MF_Redfont, &MEF_Null, &MEO_Space8, Spacer ); + +#define MAKE_MENU_TOP_ENTRYLINK( Title, Format, EntryName, LinkID ) \ +static MenuLink_t MEO_ ## EntryName = { LinkID, MA_Advance, };\ +static MenuEntry_t ME_ ## EntryName = MAKE_MENUENTRY( Title, &MF_Redfont, &Format, &MEO_ ## EntryName, Link ) + +static char const s_NewGame[] = "New Game"; +static char const s_SaveGame[] = "Save Game"; +static char const s_LoadGame[] = "Load Game"; +static char const s_Continue[] = "Continue"; +static char const s_Options[] = "Options"; +static char const s_Credits[] = "Credits"; + +MAKE_MENU_TOP_ENTRYLINK( s_NewGame, MEF_MainMenu, MAIN_NEWGAME, MENU_EPISODE ); +#ifdef EDUKE32_SIMPLE_MENU +MAKE_MENU_TOP_ENTRYLINK( "Resume Game", MEF_MainMenu, MAIN_RESUMEGAME, MENU_CLOSE ); +#endif +MAKE_MENU_TOP_ENTRYLINK( s_NewGame, MEF_MainMenu, MAIN_NEWGAME_INGAME, MENU_NEWVERIFY ); +static MenuLink_t MEO_MAIN_NEWGAME_NETWORK = { MENU_NETWORK, MA_Advance, }; +MAKE_MENU_TOP_ENTRYLINK( s_SaveGame, MEF_MainMenu, MAIN_SAVEGAME, MENU_SAVE ); +MAKE_MENU_TOP_ENTRYLINK( s_LoadGame, MEF_MainMenu, MAIN_LOADGAME, MENU_LOAD ); +MAKE_MENU_TOP_ENTRYLINK( s_Options, MEF_MainMenu, MAIN_OPTIONS, MENU_OPTIONS ); +MAKE_MENU_TOP_ENTRYLINK( "Help", MEF_MainMenu, MAIN_HELP, MENU_STORY ); +#ifndef EDUKE32_SIMPLE_MENU +MAKE_MENU_TOP_ENTRYLINK( s_Credits, MEF_MainMenu, MAIN_CREDITS, MENU_CREDITS ); +#endif +MAKE_MENU_TOP_ENTRYLINK( "End Game", MEF_MainMenu, MAIN_QUITTOTITLE, MENU_QUITTOTITLE ); +MAKE_MENU_TOP_ENTRYLINK( "Quit", MEF_MainMenu, MAIN_QUIT, MENU_QUIT ); +#ifndef EDUKE32_SIMPLE_MENU +MAKE_MENU_TOP_ENTRYLINK( "Quit Game", MEF_MainMenu, MAIN_QUITGAME, MENU_QUIT ); +#endif + +static MenuEntry_t *MEL_MAIN[] = { + &ME_MAIN_NEWGAME, + &ME_MAIN_LOADGAME, + &ME_MAIN_OPTIONS, + &ME_MAIN_HELP, +#ifndef EDUKE32_SIMPLE_MENU + &ME_MAIN_CREDITS, +#endif + &ME_MAIN_QUIT, +}; + +static MenuEntry_t *MEL_MAIN_INGAME[] = { +#ifdef EDUKE32_SIMPLE_MENU + &ME_MAIN_RESUMEGAME, +#else + &ME_MAIN_NEWGAME_INGAME, +#endif + &ME_MAIN_SAVEGAME, + &ME_MAIN_LOADGAME, + &ME_MAIN_OPTIONS, + &ME_MAIN_HELP, + &ME_MAIN_QUITTOTITLE, +#ifndef EDUKE32_SIMPLE_MENU + &ME_MAIN_QUITGAME, +#endif +}; + +// Episode and Skill will be dynamically generated after CONs are parsed +static MenuLink_t MEO_EPISODE = { MENU_SKILL, MA_Advance, }; +static MenuLink_t MEO_EPISODE_SHAREWARE = { MENU_BUYDUKE, MA_Advance, }; +static MenuEntry_t ME_EPISODE_TEMPLATE = MAKE_MENUENTRY( NULL, &MF_Redfont, &MEF_CenterMenu, &MEO_EPISODE, Link ); +static MenuEntry_t ME_EPISODE[MAXVOLUMES]; +static MenuLink_t MEO_EPISODE_USERMAP = { MENU_USERMAP, MA_Advance, }; +static MenuEntry_t ME_EPISODE_USERMAP = MAKE_MENUENTRY( "User Map", &MF_Redfont, &MEF_CenterMenu, &MEO_EPISODE_USERMAP, Link ); +static MenuEntry_t *MEL_EPISODE[MAXVOLUMES+2]; // +2 for spacer and User Map + +static MenuEntry_t ME_SKILL_TEMPLATE = MAKE_MENUENTRY( NULL, &MF_Redfont, &MEF_CenterMenu, &MEO_NULL, Link ); +static MenuEntry_t ME_SKILL[MAXSKILLS]; +static MenuEntry_t *MEL_SKILL[MAXSKILLS]; + +#if defined STARTUP_SETUP_WINDOW && !defined EDUKE32_SIMPLE_MENU +static MenuOption_t MEO_GAMESETUP_STARTWIN = MAKE_MENUOPTION( &MF_Redfont, &MEOS_OffOn, &ud.config.ForceSetup ); +static MenuEntry_t ME_GAMESETUP_STARTWIN = MAKE_MENUENTRY( "Startup window:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_GAMESETUP_STARTWIN, Option ); +#endif + +static char const *MEOSN_GAMESETUP_AIM_AUTO[] = { "Never", "Always", "Hitscan only", +#ifdef EDUKE32_ANDROID_MENU +"Extra wide" +#endif +}; +static int32_t MEOSV_GAMESETUP_AIM_AUTO[] = { 0, 1, 2, +#ifdef EDUKE32_ANDROID_MENU +3, +#endif +}; + +static MenuOptionSet_t MEOS_GAMESETUP_AIM_AUTO = MAKE_MENUOPTIONSET( MEOSN_GAMESETUP_AIM_AUTO, MEOSV_GAMESETUP_AIM_AUTO, 0x2 ); +static MenuOption_t MEO_GAMESETUP_AIM_AUTO = MAKE_MENUOPTION( &MF_Redfont, &MEOS_GAMESETUP_AIM_AUTO, &ud.config.AutoAim ); +static MenuEntry_t ME_GAMESETUP_AIM_AUTO = MAKE_MENUENTRY( "Auto aim:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_GAMESETUP_AIM_AUTO, Option ); + +static char const *MEOSN_GAMESETUP_WEAPSWITCH_PICKUP[] = { "Never", "If new", "By rating", }; +static MenuOptionSet_t MEOS_GAMESETUP_WEAPSWITCH_PICKUP = MAKE_MENUOPTIONSET( MEOSN_GAMESETUP_WEAPSWITCH_PICKUP, NULL, 0x2 ); +static MenuOption_t MEO_GAMESETUP_WEAPSWITCH_PICKUP = MAKE_MENUOPTION( &MF_Redfont, &MEOS_GAMESETUP_WEAPSWITCH_PICKUP, NULL ); +static MenuEntry_t ME_GAMESETUP_WEAPSWITCH_PICKUP = MAKE_MENUENTRY( "Equip pickups:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_GAMESETUP_WEAPSWITCH_PICKUP, Option ); + +static char const *MEOSN_DemoRec[] = { "Off", "Running", }; +static MenuOptionSet_t MEOS_DemoRec = MAKE_MENUOPTIONSET( MEOSN_DemoRec, NULL, 0x3 ); +static MenuOption_t MEO_GAMESETUP_DEMOREC = MAKE_MENUOPTION( &MF_Redfont, &MEOS_OffOn, &ud.m_recstat ); +static MenuEntry_t ME_GAMESETUP_DEMOREC = MAKE_MENUENTRY( "Record demo:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_GAMESETUP_DEMOREC, Option ); + +#ifdef _WIN32 +static MenuOption_t MEO_GAMESETUP_UPDATES = MAKE_MENUOPTION( &MF_Redfont, &MEOS_NoYes, &ud.config.CheckForUpdates ); +static MenuEntry_t ME_GAMESETUP_UPDATES = MAKE_MENUENTRY( "Online updates:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_GAMESETUP_UPDATES, Option ); +#endif + +static MenuOption_t MEO_ADULTMODE = MAKE_MENUOPTION(&MF_Redfont, &MEOS_OffOn, &ud.lockout); +static MenuEntry_t ME_ADULTMODE = MAKE_MENUENTRY( "Parental lock:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_ADULTMODE, Option ); +// static MenuLink_t MEO_ADULTMODE_PASSWORD = { MENU_ADULTPASSWORD, MA_None, }; +// static MenuEntry_t ME_ADULTMODE_PASSWORD = MAKE_MENUENTRY( "Enter Password", &MF_Redfont, &, &MEO_ADULTMODE_PASSWORD, Link ); + +#ifdef EDUKE32_ANDROID_MENU +static MenuOption_t MEO_GAMESETUP_CROUCHLOCK = MAKE_MENUOPTION(&MF_Redfont, &MEOS_OffOn, &droidinput.toggleCrouch); +static MenuEntry_t ME_GAMESETUP_CROUCHLOCK = MAKE_MENUENTRY("Crouch lock:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_GAMESETUP_CROUCHLOCK, Option); + +static MenuOption_t MEO_GAMESETUP_QUICKSWITCH = MAKE_MENUOPTION(&MF_Redfont, &MEOS_OffOn, &droidinput.quickSelectWeapon); +static MenuEntry_t ME_GAMESETUP_QUICKSWITCH = MAKE_MENUENTRY("Quick weapon switch:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_GAMESETUP_QUICKSWITCH, Option); +#endif + +#if defined(EDUKE32_ANDROID_MENU) || !defined(EDUKE32_SIMPLE_MENU) +static MenuLink_t MEO_GAMESETUP_CHEATS = { MENU_CHEATS, MA_Advance, }; +static MenuEntry_t ME_GAMESETUP_CHEATS = MAKE_MENUENTRY( "Cheats", &MF_Redfont, &MEF_BigOptionsRt, &MEO_GAMESETUP_CHEATS, Link ); + +static MenuEntry_t *MEL_GAMESETUP[] = { + &ME_ADULTMODE, +#if defined STARTUP_SETUP_WINDOW && !defined EDUKE32_SIMPLE_MENU + &ME_GAMESETUP_STARTWIN, +#endif + &ME_GAMESETUP_AIM_AUTO, + &ME_GAMESETUP_WEAPSWITCH_PICKUP, +#ifdef EDUKE32_ANDROID_MENU + &ME_GAMESETUP_QUICKSWITCH, + &ME_GAMESETUP_CROUCHLOCK, +#else + &ME_GAMESETUP_DEMOREC, +#ifdef _WIN32 + &ME_GAMESETUP_UPDATES, +#endif +#endif + &ME_GAMESETUP_CHEATS, +}; +#endif + +#ifndef EDUKE32_SIMPLE_MENU +MAKE_MENU_TOP_ENTRYLINK( "Game Setup", MEF_OptionsMenu, OPTIONS_GAMESETUP, MENU_GAMESETUP ); +#endif +MAKE_MENU_TOP_ENTRYLINK( "Sound Setup", MEF_OptionsMenu, OPTIONS_SOUNDSETUP, MENU_SOUND ); +MAKE_MENU_TOP_ENTRYLINK( "Display Setup", MEF_OptionsMenu, OPTIONS_DISPLAYSETUP, MENU_DISPLAYSETUP ); +MAKE_MENU_TOP_ENTRYLINK( "Player Setup", MEF_OptionsMenu, OPTIONS_PLAYERSETUP, MENU_PLAYER ); +#ifndef EDUKE32_ANDROID_MENU +MAKE_MENU_TOP_ENTRYLINK( "Control Setup", MEF_OptionsMenu, OPTIONS_CONTROLS, MENU_CONTROLS ); + +MAKE_MENU_TOP_ENTRYLINK( "Keyboard Setup", MEF_CenterMenu, OPTIONS_KEYBOARDSETUP, MENU_KEYBOARDSETUP ); +MAKE_MENU_TOP_ENTRYLINK( "Mouse Setup", MEF_CenterMenu, OPTIONS_MOUSESETUP, MENU_MOUSESETUP ); +#endif +MAKE_MENU_TOP_ENTRYLINK( "Joystick Setup", MEF_CenterMenu, OPTIONS_JOYSTICKSETUP, MENU_JOYSTICKSETUP ); +#ifdef EDUKE32_ANDROID_MENU +MAKE_MENU_TOP_ENTRYLINK( "Touch Setup", MEF_CenterMenu, OPTIONS_TOUCHSETUP, MENU_TOUCHSETUP ); +#endif +#ifdef EDUKE32_SIMPLE_MENU +MAKE_MENU_TOP_ENTRYLINK("Cheats", MEF_OptionsMenu, OPTIONS_CHEATS, MENU_CHEATS); +#endif + +static int32_t newresolution, newrendermode, newfullscreen, newvsync; + +enum resflags_t { + RES_FS = 0x1, + RES_WIN = 0x2, +}; + +#define MAXRESOLUTIONSTRINGLENGTH 16 + +typedef struct resolution_t { + int32_t xdim, ydim; + int32_t flags; + int32_t bppmax; + char name[MAXRESOLUTIONSTRINGLENGTH]; +} resolution_t; + +resolution_t resolution[MAXVALIDMODES]; + +static char const *MEOSN_VIDEOSETUP_RESOLUTION[MAXVALIDMODES]; +static MenuOptionSet_t MEOS_VIDEOSETUP_RESOLUTION = MAKE_MENUOPTIONSETDYN( MEOSN_VIDEOSETUP_RESOLUTION, NULL, 0, 0x0 ); +static MenuOption_t MEO_VIDEOSETUP_RESOLUTION = MAKE_MENUOPTION( &MF_Redfont, &MEOS_VIDEOSETUP_RESOLUTION, &newresolution ); +static MenuEntry_t ME_VIDEOSETUP_RESOLUTION = MAKE_MENUENTRY( "Resolution:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_VIDEOSETUP_RESOLUTION, Option ); + +#ifdef USE_OPENGL +#ifdef POLYMER +static char const *MEOSN_VIDEOSETUP_RENDERER[] = { "Classic", "Polymost", "Polymer", }; +static int32_t MEOSV_VIDEOSETUP_RENDERER[] = { REND_CLASSIC, REND_POLYMOST, REND_POLYMER, }; +#else +static char const *MEOSN_VIDEOSETUP_RENDERER[] = { "Classic", "OpenGL", }; +static int32_t MEOSV_VIDEOSETUP_RENDERER[] = { REND_CLASSIC, REND_POLYMOST, }; +#endif + +static MenuOptionSet_t MEOS_VIDEOSETUP_RENDERER = MAKE_MENUOPTIONSET( MEOSN_VIDEOSETUP_RENDERER, MEOSV_VIDEOSETUP_RENDERER, 0x2 ); + +static MenuOption_t MEO_VIDEOSETUP_RENDERER = MAKE_MENUOPTION( &MF_Redfont, &MEOS_VIDEOSETUP_RENDERER, &newrendermode ); +static MenuEntry_t ME_VIDEOSETUP_RENDERER = MAKE_MENUENTRY( "Renderer:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_VIDEOSETUP_RENDERER, Option ); +#endif + +static MenuOption_t MEO_VIDEOSETUP_FULLSCREEN = MAKE_MENUOPTION( &MF_Redfont, &MEOS_NoYes, &newfullscreen ); +static MenuEntry_t ME_VIDEOSETUP_FULLSCREEN = MAKE_MENUENTRY( "Fullscreen:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_VIDEOSETUP_FULLSCREEN, Option ); + + +static char const *MEOSN_VIDEOSETUP_VSYNC [] = { "Adaptive", "Off", "On", }; +static int32_t MEOSV_VIDEOSETUP_VSYNC [] = { -1, 0, 1, }; +static MenuOptionSet_t MEOS_VIDEOSETUP_VSYNC = MAKE_MENUOPTIONSET(MEOSN_VIDEOSETUP_VSYNC, MEOSV_VIDEOSETUP_VSYNC, 0x2); +static MenuOption_t MEO_VIDEOSETUP_VSYNC = MAKE_MENUOPTION(&MF_Redfont, &MEOS_VIDEOSETUP_VSYNC, &newvsync); +static MenuEntry_t ME_VIDEOSETUP_VSYNC = MAKE_MENUENTRY("VSync:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_VIDEOSETUP_VSYNC, Option); + +static char const *MEOSN_VIDEOSETUP_FRAMELIMIT [] = { "None", "30 fps", "60 fps", "120 fps", "144 fps", }; +static int32_t MEOSV_VIDEOSETUP_FRAMELIMIT [] = { 0, 30, 60, 120, 144 }; +static MenuOptionSet_t MEOS_VIDEOSETUP_FRAMELIMIT = MAKE_MENUOPTIONSET(MEOSN_VIDEOSETUP_FRAMELIMIT, MEOSV_VIDEOSETUP_FRAMELIMIT, 0x2); +static MenuOption_t MEO_VIDEOSETUP_FRAMELIMIT= MAKE_MENUOPTION(&MF_Redfont, &MEOS_VIDEOSETUP_FRAMELIMIT, &r_maxfps); +static MenuEntry_t ME_VIDEOSETUP_FRAMELIMIT = MAKE_MENUENTRY("Framerate limit:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_VIDEOSETUP_FRAMELIMIT, Option); + + +static MenuEntry_t ME_VIDEOSETUP_APPLY = MAKE_MENUENTRY( "Apply Changes", &MF_Redfont, &MEF_BigOptions_Apply, &MEO_NULL, Link ); + + +static MenuLink_t MEO_DISPLAYSETUP_COLORCORR = { MENU_COLCORR, MA_Advance, }; +static MenuEntry_t ME_DISPLAYSETUP_COLORCORR = MAKE_MENUENTRY( "Color Correction", &MF_Redfont, &MEF_BigOptionsRt, &MEO_DISPLAYSETUP_COLORCORR, Link ); + + +static MenuOption_t MEO_DISPLAYSETUP_PIXELDOUBLING = MAKE_MENUOPTION( &MF_Redfont, &MEOS_OffOn, &ud.detail ); +static MenuEntry_t ME_DISPLAYSETUP_PIXELDOUBLING = MAKE_MENUENTRY( "Pixel Doubling:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_DISPLAYSETUP_PIXELDOUBLING, Option ); + + +#ifndef EDUKE32_ANDROID_MENU +static MenuOption_t MEO_DISPLAYSETUP_ASPECTRATIO = MAKE_MENUOPTION(&MF_Redfont, &MEOS_OffOn, &r_usenewaspect); +static MenuEntry_t ME_DISPLAYSETUP_ASPECTRATIO = MAKE_MENUENTRY( "Widescreen:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_DISPLAYSETUP_ASPECTRATIO, Option ); +#endif + + +#ifdef USE_OPENGL +#ifdef EDUKE32_SIMPLE_MENU +static MenuOption_t MEO_DISPLAYSETUP_PALETTEEMULATION = MAKE_MENUOPTION(&MF_Redfont, &MEOS_OffOn, &r_usetileshades); +static MenuEntry_t ME_DISPLAYSETUP_PALETTEEMULATION = MAKE_MENUENTRY("Palette emulation:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_DISPLAYSETUP_PALETTEEMULATION, Option); +#endif + +static char const *MEOSN_DISPLAYSETUP_TEXFILTER[] = { "Classic", "Filtered" }; +static int32_t MEOSV_DISPLAYSETUP_TEXFILTER[] = { TEXFILTER_OFF, TEXFILTER_ON }; +static MenuOptionSet_t MEOS_DISPLAYSETUP_TEXFILTER = MAKE_MENUOPTIONSET( MEOSN_DISPLAYSETUP_TEXFILTER, MEOSV_DISPLAYSETUP_TEXFILTER, 0x2 ); +static MenuOption_t MEO_DISPLAYSETUP_TEXFILTER = MAKE_MENUOPTION( &MF_Redfont, &MEOS_DISPLAYSETUP_TEXFILTER, &gltexfiltermode ); +static MenuEntry_t ME_DISPLAYSETUP_TEXFILTER = MAKE_MENUENTRY( "Texture Mode:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_DISPLAYSETUP_TEXFILTER, Option ); + +static char const *MEOSN_DISPLAYSETUP_ANISOTROPY[] = { "Max", "None", "2x", "4x", "8x", "16x", }; +static int32_t MEOSV_DISPLAYSETUP_ANISOTROPY[] = { 0, 1, 2, 4, 8, 16, }; +static MenuOptionSet_t MEOS_DISPLAYSETUP_ANISOTROPY = MAKE_MENUOPTIONSET( MEOSN_DISPLAYSETUP_ANISOTROPY, MEOSV_DISPLAYSETUP_ANISOTROPY, 0x0 ); +static MenuOption_t MEO_DISPLAYSETUP_ANISOTROPY = MAKE_MENUOPTION(&MF_Redfont, &MEOS_DISPLAYSETUP_ANISOTROPY, &glanisotropy); +static MenuEntry_t ME_DISPLAYSETUP_ANISOTROPY = MAKE_MENUENTRY( "Anisotropy:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_DISPLAYSETUP_ANISOTROPY, Option ); + +#ifdef EDUKE32_ANDROID_MENU +static MenuOption_t MEO_DISPLAYSETUP_HIDEDPAD = MAKE_MENUOPTION(&MF_Redfont, &MEOS_NoYes, &droidinput.hideStick); +static MenuEntry_t ME_DISPLAYSETUP_HIDEDPAD = MAKE_MENUENTRY("Hide touch D-pad:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_DISPLAYSETUP_HIDEDPAD, Option); + +static MenuRangeFloat_t MEO_DISPLAYSETUP_TOUCHALPHA = MAKE_MENURANGE(&droidinput.gameControlsAlpha, &MF_Redfont, 0, 1, 0, 16, 2); +static MenuEntry_t ME_DISPLAYSETUP_TOUCHALPHA = MAKE_MENUENTRY("UI opacity:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_DISPLAYSETUP_TOUCHALPHA, RangeFloat); +#endif + +#endif + +static char const s_Scale[] = "Scale:"; + +static MenuOption_t MEO_SCREENSETUP_CROSSHAIR = MAKE_MENUOPTION(&MF_Redfont, &MEOS_OffOn, &ud.crosshair); +static MenuEntry_t ME_SCREENSETUP_CROSSHAIR = MAKE_MENUENTRY( "Crosshair:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_SCREENSETUP_CROSSHAIR, Option ); +static MenuRangeInt32_t MEO_SCREENSETUP_CROSSHAIRSIZE = MAKE_MENURANGE( &ud.crosshairscale, &MF_Redfont, 25, 100, 0, 16, 2 ); +static MenuEntry_t ME_SCREENSETUP_CROSSHAIRSIZE = MAKE_MENUENTRY( s_Scale, &MF_Redfont, &MEF_BigOptions_Apply, &MEO_SCREENSETUP_CROSSHAIRSIZE, RangeInt32 ); + +static int32_t vpsize; +static MenuRangeInt32_t MEO_SCREENSETUP_SCREENSIZE = MAKE_MENURANGE( &vpsize, &MF_Redfont, 16, 0, 0, 5, EnforceIntervals ); +static MenuEntry_t ME_SCREENSETUP_SCREENSIZE = MAKE_MENUENTRY( "Screen size:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_SCREENSETUP_SCREENSIZE, RangeInt32 ); +static MenuRangeInt32_t MEO_SCREENSETUP_TEXTSIZE = MAKE_MENURANGE( &ud.textscale, &MF_Redfont, 100, 400, 0, 16, 2 ); +static MenuEntry_t ME_SCREENSETUP_TEXTSIZE = MAKE_MENUENTRY( s_Scale, &MF_Redfont, &MEF_BigOptions_Apply, &MEO_SCREENSETUP_TEXTSIZE, RangeInt32 ); +static MenuOption_t MEO_SCREENSETUP_LEVELSTATS = MAKE_MENUOPTION(&MF_Redfont, &MEOS_OffOn, &ud.levelstats); +static MenuEntry_t ME_SCREENSETUP_LEVELSTATS = MAKE_MENUENTRY( "Level stats:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_SCREENSETUP_LEVELSTATS, Option ); + + +static MenuOption_t MEO_SCREENSETUP_SHOWPICKUPMESSAGES = MAKE_MENUOPTION(&MF_Redfont, &MEOS_OffOn, &ud.fta_on); +static MenuEntry_t ME_SCREENSETUP_SHOWPICKUPMESSAGES = MAKE_MENUENTRY( "Game messages:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_SCREENSETUP_SHOWPICKUPMESSAGES, Option ); + + + +static MenuRangeInt32_t MEO_SCREENSETUP_SBARSIZE = MAKE_MENURANGE( &ud.statusbarscale, &MF_Redfont, 36, 100, 0, 17, 2 ); +static MenuEntry_t ME_SCREENSETUP_SBARSIZE = MAKE_MENUENTRY( s_Scale, &MF_Redfont, &MEF_BigOptions_Apply, &MEO_SCREENSETUP_SBARSIZE, RangeInt32 ); + + +static MenuLink_t MEO_DISPLAYSETUP_SCREENSETUP = { MENU_SCREENSETUP, MA_Advance, }; +static MenuEntry_t ME_DISPLAYSETUP_SCREENSETUP = MAKE_MENUENTRY( "HUD setup", &MF_Redfont, &MEF_BigOptionsRt, &MEO_DISPLAYSETUP_SCREENSETUP, Link ); + + +#ifndef EDUKE32_SIMPLE_MENU +#ifdef USE_OPENGL +static MenuLink_t MEO_DISPLAYSETUP_ADVANCED_GL_POLYMOST = { MENU_POLYMOST, MA_Advance, }; +static MenuEntry_t ME_DISPLAYSETUP_ADVANCED_GL_POLYMOST = MAKE_MENUENTRY( "Polymost setup", &MF_Redfont, &MEF_BigOptionsRt, &MEO_DISPLAYSETUP_ADVANCED_GL_POLYMOST, Link ); + +#ifdef POLYMER +static MenuLink_t MEO_DISPLAYSETUP_ADVANCED_GL_POLYMER = { MENU_POLYMER, MA_Advance, }; +static MenuEntry_t ME_DISPLAYSETUP_ADVANCED_GL_POLYMER = MAKE_MENUENTRY("Polymer setup", &MF_Redfont, &MEF_BigOptionsRt, &MEO_DISPLAYSETUP_ADVANCED_GL_POLYMER, Link); +#endif +#endif +#endif + +#ifndef EDUKE32_ANDROID_MENU +static MenuLink_t MEO_DISPLAYSETUP_VIDEOSETUP = { MENU_VIDEOSETUP, MA_Advance, }; +static MenuEntry_t ME_DISPLAYSETUP_VIDEOSETUP = MAKE_MENUENTRY( "Video mode", &MF_Redfont, &MEF_BigOptionsRt, &MEO_DISPLAYSETUP_VIDEOSETUP, Link ); +#endif + + +static MenuLink_t MEO_ENTERCHEAT = { MENU_CHEATENTRY, MA_None, }; +static MenuEntry_t ME_ENTERCHEAT = MAKE_MENUENTRY( "Enter Cheat Code", &MF_Redfont, &MEF_BigCheats, &MEO_ENTERCHEAT, Link ); + +static MenuLink_t MEO_CHEAT_WARP = { MENU_CHEAT_WARP, MA_None, }; +static MenuLink_t MEO_CHEAT_SKILL = { MENU_CHEAT_SKILL, MA_None, }; +// KEEPINSYNC game.h: enum CheatCodeFunctions +// KEEPINSYNC game.c: uint8_t CheatFunctionIDs[] +#define MAKE_MENUCHEAT( Name ) MAKE_MENUENTRY( Name, &MF_Bluefont, &MEF_Cheats, &MEO_NULL, Link ) +static MenuEntry_t ME_CheatCodes[] = { + MAKE_MENUCHEAT( "Toggle Cashman" ), + MAKE_MENUCHEAT( "Toggle God Mode" ), + MAKE_MENUCHEAT( "Give Everything" ), + MAKE_MENUCHEAT( "Give Weapons" ), + MAKE_MENUCHEAT( "Give All Items" ), + MAKE_MENUCHEAT( "Give Inventory" ), + MAKE_MENUCHEAT( "Give Keys" ), + MAKE_MENUCHEAT( "Toggle Hyper" ), + MAKE_MENUCHEAT( "Toggle 3rd-Person View" ), + MAKE_MENUCHEAT( "Toggle Show All Map" ), + MAKE_MENUCHEAT( "Toggle All Locks" ), + MAKE_MENUCHEAT( "Toggle Clipping" ), + MAKE_MENUENTRY( "Level Warp", &MF_Bluefont, &MEF_Cheats, &MEO_CHEAT_WARP, Link ), + MAKE_MENUENTRY( "Change Skill", &MF_Bluefont, &MEF_Cheats, &MEO_CHEAT_SKILL, Link ), + MAKE_MENUCHEAT( "Toggle Monsters" ), + MAKE_MENUCHEAT( "Toggle Framerate Display" ), + MAKE_MENUCHEAT( NULL ), + MAKE_MENUCHEAT( NULL ), + MAKE_MENUCHEAT( NULL ), + MAKE_MENUCHEAT( "Toggle Coordinate Display" ), + MAKE_MENUCHEAT( "Toggle Debug Data Dump" ), +}; + +static MenuEntry_t *MEL_OPTIONS[] = { +#ifndef EDUKE32_SIMPLE_MENU + &ME_OPTIONS_GAMESETUP, +#endif + &ME_OPTIONS_DISPLAYSETUP, + &ME_OPTIONS_SOUNDSETUP, +#ifndef EDUKE32_ANDROID_MENU +#ifndef EDUKE32_SIMPLE_MENU + &ME_OPTIONS_PLAYERSETUP, +#endif + &ME_OPTIONS_CONTROLS, +#else + &ME_OPTIONS_TOUCHSETUP, +#endif +#ifdef EDUKE32_SIMPLE_MENU + &ME_OPTIONS_CHEATS +#endif +}; + +static MenuEntry_t *MEL_CONTROLS[] = { +#ifndef EDUKE32_ANDROID_MENU + &ME_OPTIONS_KEYBOARDSETUP, + &ME_OPTIONS_MOUSESETUP, + &ME_OPTIONS_JOYSTICKSETUP, +#else + &ME_OPTIONS_TOUCHSETUP +#endif +}; + +static MenuEntry_t *MEL_CHEATS[ARRAY_SIZE(ME_CheatCodes)+1] = { + &ME_ENTERCHEAT, +}; + +static MenuEntry_t *MEL_VIDEOSETUP[] = { + &ME_VIDEOSETUP_RESOLUTION, +#ifdef USE_OPENGL + &ME_VIDEOSETUP_RENDERER, +#endif + &ME_VIDEOSETUP_FULLSCREEN, + &ME_VIDEOSETUP_VSYNC, + &ME_VIDEOSETUP_FRAMELIMIT, + &ME_Space6_Redfont, + &ME_VIDEOSETUP_APPLY, +}; +static MenuEntry_t *MEL_DISPLAYSETUP[] = { + &ME_DISPLAYSETUP_SCREENSETUP, + &ME_DISPLAYSETUP_COLORCORR, +#ifndef EDUKE32_ANDROID_MENU + &ME_DISPLAYSETUP_VIDEOSETUP, + &ME_DISPLAYSETUP_ASPECTRATIO, +#endif + &ME_DISPLAYSETUP_PIXELDOUBLING, +}; + +#ifdef USE_OPENGL +static MenuEntry_t *MEL_DISPLAYSETUP_GL[] = { + &ME_DISPLAYSETUP_SCREENSETUP, + &ME_DISPLAYSETUP_COLORCORR, +#ifndef EDUKE32_ANDROID_MENU + &ME_DISPLAYSETUP_VIDEOSETUP, + &ME_DISPLAYSETUP_ASPECTRATIO, +#endif + &ME_DISPLAYSETUP_TEXFILTER, +#ifdef EDUKE32_ANDROID_MENU + &ME_DISPLAYSETUP_HIDEDPAD, + &ME_DISPLAYSETUP_TOUCHALPHA, +#else + &ME_DISPLAYSETUP_ANISOTROPY, +#ifdef EDUKE32_SIMPLE_MENU + &ME_DISPLAYSETUP_PALETTEEMULATION, +#else + &ME_DISPLAYSETUP_ADVANCED_GL_POLYMOST, +#endif +#endif +}; + +#ifdef POLYMER +static MenuEntry_t *MEL_DISPLAYSETUP_GL_POLYMER[] = { + &ME_DISPLAYSETUP_SCREENSETUP, + &ME_DISPLAYSETUP_COLORCORR, +#ifndef EDUKE32_ANDROID_MENU + &ME_DISPLAYSETUP_VIDEOSETUP, +#endif + &ME_DISPLAYSETUP_TEXFILTER, + &ME_DISPLAYSETUP_ANISOTROPY, +#ifndef EDUKE32_SIMPLE_MENU + &ME_DISPLAYSETUP_ADVANCED_GL_POLYMER, +#endif +}; + +#endif +#endif + + + +static char const *MenuKeyNone = " -"; +static char const *MEOSN_Keys[NUMKEYS]; + +static MenuCustom2Col_t MEO_KEYBOARDSETUPFUNCS_TEMPLATE = { { NULL, NULL, }, MEOSN_Keys, &MF_MinifontRed, NUMKEYS, 54<<16, 0 }; +static MenuCustom2Col_t MEO_KEYBOARDSETUPFUNCS[NUMGAMEFUNCTIONS]; +static MenuEntry_t ME_KEYBOARDSETUPFUNCS_TEMPLATE = MAKE_MENUENTRY( NULL, &MF_Minifont, &MEF_FuncList, &MEO_KEYBOARDSETUPFUNCS_TEMPLATE, Custom2Col ); +static MenuEntry_t ME_KEYBOARDSETUPFUNCS[NUMGAMEFUNCTIONS]; +static MenuEntry_t *MEL_KEYBOARDSETUPFUNCS[NUMGAMEFUNCTIONS]; + +static MenuLink_t MEO_KEYBOARDSETUP_KEYS = { MENU_KEYBOARDKEYS, MA_Advance, }; +static MenuEntry_t ME_KEYBOARDSETUP_KEYS = MAKE_MENUENTRY( "Configure Keys", &MF_Redfont, &MEF_CenterMenu, &MEO_KEYBOARDSETUP_KEYS, Link ); +static MenuEntry_t ME_KEYBOARDSETUP_RESET = MAKE_MENUENTRY( "Reset To Defaults", &MF_Redfont, &MEF_CenterMenu, &MEO_NULL, Link ); +static MenuEntry_t ME_KEYBOARDSETUP_RESETCLASSIC = MAKE_MENUENTRY( "Reset To Classic", &MF_Redfont, &MEF_CenterMenu, &MEO_NULL, Link ); + +static MenuEntry_t *MEL_KEYBOARDSETUP[] = { + &ME_KEYBOARDSETUP_KEYS, + &ME_KEYBOARDSETUP_RESET, + &ME_KEYBOARDSETUP_RESETCLASSIC, +}; + + +// There is no better way to do this than manually. + +#define MENUMOUSEFUNCTIONS 12 + +static char const *MenuMouseNames[MENUMOUSEFUNCTIONS] = { + "Button 1", + "Double Button 1", + "Button 2", + "Double Button 2", + "Button 3", + "Double Button 3", + + "Wheel Up", + "Wheel Down", + + "Button 4", + "Double Button 4", + "Button 5", + "Double Button 5", +}; +static int32_t MenuMouseDataIndex[MENUMOUSEFUNCTIONS][2] = { + { 0, 0, }, + { 0, 1, }, + { 1, 0, }, + { 1, 1, }, + { 2, 0, }, + { 2, 1, }, + + // note the mouse wheel + { 4, 0, }, + { 5, 0, }, + + { 3, 0, }, + { 3, 1, }, + { 6, 0, }, + { 6, 1, }, +}; + +static MenuOption_t MEO_MOUSEJOYSETUPBTNS_TEMPLATE = MAKE_MENUOPTION( &MF_Minifont, &MEOS_Gamefuncs, NULL ); +static MenuOption_t MEO_MOUSESETUPBTNS[MENUMOUSEFUNCTIONS]; +static MenuEntry_t ME_MOUSEJOYSETUPBTNS_TEMPLATE = MAKE_MENUENTRY( NULL, &MF_Minifont, &MEF_FuncList, NULL, Option ); +static MenuEntry_t ME_MOUSESETUPBTNS[MENUMOUSEFUNCTIONS]; +static MenuEntry_t *MEL_MOUSESETUPBTNS[MENUMOUSEFUNCTIONS]; + +static MenuLink_t MEO_MOUSESETUP_BTNS = { MENU_MOUSEBTNS, MA_Advance, }; +static MenuEntry_t ME_MOUSESETUP_BTNS = MAKE_MENUENTRY( "Button assignment", &MF_Redfont, &MEF_BigOptionsRt, &MEO_MOUSESETUP_BTNS, Link ); +static MenuRangeFloat_t MEO_MOUSESETUP_SENSITIVITY = MAKE_MENURANGE( &CONTROL_MouseSensitivity, &MF_Redfont, .5f, 16.f, 0.f, 32, 1 ); +static MenuEntry_t ME_MOUSESETUP_SENSITIVITY = MAKE_MENUENTRY( "Sensitivity:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_MOUSESETUP_SENSITIVITY, RangeFloat ); + +#ifndef EDUKE32_SIMPLE_MENU +static char const *MEOSN_MOUSESETUP_AIM_TYPE [] = { "Toggle", "Hold" }; +static MenuOptionSet_t MEOS_MOUSESETUP_AIM_TYPE = MAKE_MENUOPTIONSET(MEOSN_MOUSESETUP_AIM_TYPE, NULL, 0x2); +static MenuOption_t MEO_MOUSESETUP_MOUSEAIMINGTYPE = MAKE_MENUOPTION(&MF_Redfont, &MEOS_MOUSESETUP_AIM_TYPE, &ud.mouseaiming); +static MenuEntry_t ME_MOUSESETUP_MOUSEAIMINGTYPE = MAKE_MENUENTRY("Aiming type:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_MOUSESETUP_MOUSEAIMINGTYPE, Option); +static MenuOption_t MEO_MOUSESETUP_MOUSEAIMING = MAKE_MENUOPTION( &MF_Redfont, &MEOS_NoYes, &g_myAimMode ); +static MenuEntry_t ME_MOUSESETUP_MOUSEAIMING = MAKE_MENUENTRY( "Vertical aiming:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_MOUSESETUP_MOUSEAIMING, Option ); +#endif +static MenuOption_t MEO_MOUSESETUP_INVERT = MAKE_MENUOPTION( &MF_Redfont, &MEOS_YesNo, &ud.mouseflip ); +static MenuEntry_t ME_MOUSESETUP_INVERT = MAKE_MENUENTRY( "Invert aiming:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_MOUSESETUP_INVERT, Option ); +static MenuOption_t MEO_MOUSESETUP_SMOOTH = MAKE_MENUOPTION( &MF_Redfont, &MEOS_NoYes, &ud.config.SmoothInput ); +static MenuEntry_t ME_MOUSESETUP_SMOOTH = MAKE_MENUENTRY( "Filter input:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_MOUSESETUP_SMOOTH, Option ); +#ifndef EDUKE32_SIMPLE_MENU +static MenuLink_t MEO_MOUSESETUP_ADVANCED = { MENU_MOUSEADVANCED, MA_Advance, }; +static MenuEntry_t ME_MOUSESETUP_ADVANCED = MAKE_MENUENTRY( "Advanced setup", &MF_Redfont, &MEF_BigOptionsRt, &MEO_MOUSESETUP_ADVANCED, Link ); +#endif +static MenuRangeInt32_t MEO_MOUSEADVANCED_SCALEX = MAKE_MENURANGE(&ud.config.MouseAnalogueScale[0], &MF_Redfont, 512, 65536, 65536, 63, 3); +static MenuEntry_t ME_MOUSEADVANCED_SCALEX = MAKE_MENUENTRY("X-Axis Scale:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_MOUSEADVANCED_SCALEX, RangeInt32); +static MenuRangeInt32_t MEO_MOUSEADVANCED_SCALEY = MAKE_MENURANGE(&ud.config.MouseAnalogueScale[1], &MF_Redfont, 512, 65536, 65536, 63, 3); +static MenuEntry_t ME_MOUSEADVANCED_SCALEY = MAKE_MENUENTRY("Y-Axis Scale:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_MOUSEADVANCED_SCALEY, RangeInt32); + +static MenuEntry_t *MEL_MOUSESETUP[] = { + &ME_MOUSESETUP_BTNS, + &ME_MOUSESETUP_SENSITIVITY, +#ifdef EDUKE32_SIMPLE_MENU + &ME_MOUSEADVANCED_SCALEX, + &ME_MOUSEADVANCED_SCALEY, +#endif + &ME_Space2_Redfont, +#ifdef EDUKE32_SIMPLE_MENU + &ME_GAMESETUP_AIM_AUTO, +#endif + &ME_MOUSESETUP_INVERT, + &ME_MOUSESETUP_SMOOTH, +#ifndef EDUKE32_SIMPLE_MENU + &ME_MOUSESETUP_MOUSEAIMINGTYPE, + &ME_MOUSESETUP_MOUSEAIMING, + &ME_MOUSESETUP_ADVANCED, +#endif +}; + +#ifdef EDUKE32_ANDROID_MENU +static MenuRangeFloat_t MEO_TOUCHSETUP_SENSITIVITY_MOVE = MAKE_MENURANGE(&droidinput.forward_sens, &MF_Redfont, 1.f, 9.f, 0.f, 17, 1 + EnforceIntervals); +static MenuEntry_t ME_TOUCHSETUP_SENSITIVITY_MOVE = MAKE_MENUENTRY("Running:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_TOUCHSETUP_SENSITIVITY_MOVE, RangeFloat); + +static MenuRangeFloat_t MEO_TOUCHSETUP_SENSITIVITY_STRAFE = MAKE_MENURANGE(&droidinput.strafe_sens, &MF_Redfont, 1.f, 9.f, 0.f, 17, 1 + EnforceIntervals); +static MenuEntry_t ME_TOUCHSETUP_SENSITIVITY_STRAFE = MAKE_MENUENTRY("Strafing:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_TOUCHSETUP_SENSITIVITY_STRAFE, RangeFloat); + +static MenuRangeFloat_t MEO_TOUCHSETUP_SENSITIVITY_LOOK = MAKE_MENURANGE(&droidinput.pitch_sens, &MF_Redfont, 1.f, 9.f, 0.f, 17, 1 + EnforceIntervals); +static MenuEntry_t ME_TOUCHSETUP_SENSITIVITY_LOOK = MAKE_MENUENTRY("Looking:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_TOUCHSETUP_SENSITIVITY_LOOK, RangeFloat); + +static MenuRangeFloat_t MEO_TOUCHSETUP_SENSITIVITY_TURN = MAKE_MENURANGE(&droidinput.yaw_sens, &MF_Redfont, 1.f, 9.f, 0.f, 17, 1 + EnforceIntervals); +static MenuEntry_t ME_TOUCHSETUP_SENSITIVITY_TURN = MAKE_MENUENTRY("Turning:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_TOUCHSETUP_SENSITIVITY_TURN, RangeFloat); + +static MenuOption_t MEO_TOUCHSETUP_INVERT = MAKE_MENUOPTION(&MF_Redfont, &MEOS_NoYes, &droidinput.invertLook); +static MenuEntry_t ME_TOUCHSETUP_INVERT = MAKE_MENUENTRY("Invert look:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_TOUCHSETUP_INVERT, Option); + +MAKE_MENU_TOP_ENTRYLINK("Sensitivity", MEF_CenterMenu, TOUCHSENS, MENU_TOUCHSENS); +MAKE_MENU_TOP_ENTRYLINK("Button Setup", MEF_CenterMenu, TOUCHBUTTONS, MENU_TOUCHBUTTONS); + +static MenuEntry_t *MEL_TOUCHSETUP [] = { + &ME_TOUCHSENS, + &ME_TOUCHBUTTONS, +}; + +static MenuEntry_t *MEL_TOUCHSENS [] = { + &ME_TOUCHSETUP_SENSITIVITY_MOVE, + &ME_TOUCHSETUP_SENSITIVITY_STRAFE, + &ME_TOUCHSETUP_SENSITIVITY_LOOK, + &ME_TOUCHSETUP_SENSITIVITY_TURN, + &ME_Space2_Redfont, + &ME_TOUCHSETUP_INVERT, +}; +#endif + +MAKE_MENU_TOP_ENTRYLINK( "Edit Buttons", MEF_CenterMenu, JOYSTICK_EDITBUTTONS, MENU_JOYSTICKBTNS ); +MAKE_MENU_TOP_ENTRYLINK( "Edit Axes", MEF_CenterMenu, JOYSTICK_EDITAXES, MENU_JOYSTICKAXES ); + +static MenuEntry_t *MEL_JOYSTICKSETUP[] = { + &ME_JOYSTICK_EDITBUTTONS, + &ME_JOYSTICK_EDITAXES, +}; + +#define MAXJOYBUTTONSTRINGLENGTH 32 + +static char MenuJoystickNames[MAXJOYBUTTONSANDHATS*2][MAXJOYBUTTONSTRINGLENGTH]; + +static MenuOption_t MEO_JOYSTICKBTNS[MAXJOYBUTTONSANDHATS*2]; +static MenuEntry_t ME_JOYSTICKBTNS[MAXJOYBUTTONSANDHATS*2]; +static MenuEntry_t *MEL_JOYSTICKBTNS[MAXJOYBUTTONSANDHATS*2]; + +static MenuLink_t MEO_JOYSTICKAXES = { MENU_JOYSTICKAXIS, MA_Advance, }; +static MenuEntry_t ME_JOYSTICKAXES_TEMPLATE = MAKE_MENUENTRY( NULL, &MF_Redfont, &MEF_BigSliders, &MEO_JOYSTICKAXES, Link ); +static MenuEntry_t ME_JOYSTICKAXES[MAXJOYAXES]; +static char MenuJoystickAxes[MAXJOYAXES][MAXJOYBUTTONSTRINGLENGTH]; + +static MenuEntry_t *MEL_JOYSTICKAXES[MAXJOYAXES]; + +static MenuOption_t MEO_MOUSEADVANCED_DAXES_UP = MAKE_MENUOPTION( &MF_BluefontRed, &MEOS_Gamefuncs, &ud.config.MouseDigitalFunctions[1][0] ); +static MenuEntry_t ME_MOUSEADVANCED_DAXES_UP = MAKE_MENUENTRY( "Digital Up", &MF_Redfont, &MEF_BigSliders, &MEO_MOUSEADVANCED_DAXES_UP, Option ); +static MenuOption_t MEO_MOUSEADVANCED_DAXES_DOWN = MAKE_MENUOPTION( &MF_BluefontRed, &MEOS_Gamefuncs, &ud.config.MouseDigitalFunctions[1][1] ); +static MenuEntry_t ME_MOUSEADVANCED_DAXES_DOWN = MAKE_MENUENTRY( "Digital Down", &MF_Redfont, &MEF_BigSliders, &MEO_MOUSEADVANCED_DAXES_DOWN, Option ); +static MenuOption_t MEO_MOUSEADVANCED_DAXES_LEFT = MAKE_MENUOPTION( &MF_BluefontRed, &MEOS_Gamefuncs, &ud.config.MouseDigitalFunctions[0][0] ); +static MenuEntry_t ME_MOUSEADVANCED_DAXES_LEFT = MAKE_MENUENTRY( "Digital Left", &MF_Redfont, &MEF_BigSliders, &MEO_MOUSEADVANCED_DAXES_LEFT, Option ); +static MenuOption_t MEO_MOUSEADVANCED_DAXES_RIGHT = MAKE_MENUOPTION( &MF_BluefontRed, &MEOS_Gamefuncs, &ud.config.MouseDigitalFunctions[0][1] ); +static MenuEntry_t ME_MOUSEADVANCED_DAXES_RIGHT = MAKE_MENUENTRY( "Digital Right", &MF_Redfont, &MEF_BigSliders, &MEO_MOUSEADVANCED_DAXES_RIGHT, Option ); + +static MenuEntry_t *MEL_MOUSEADVANCED[] = { + &ME_MOUSEADVANCED_SCALEX, + &ME_MOUSEADVANCED_SCALEY, + &ME_Space8_Redfont, + &ME_MOUSEADVANCED_DAXES_UP, + &ME_MOUSEADVANCED_DAXES_DOWN, + &ME_MOUSEADVANCED_DAXES_LEFT, + &ME_MOUSEADVANCED_DAXES_RIGHT, +}; + +static MenuEntry_t *MEL_INTERNAL_MOUSEADVANCED_DAXES[] = { + &ME_MOUSEADVANCED_DAXES_UP, + &ME_MOUSEADVANCED_DAXES_DOWN, + &ME_MOUSEADVANCED_DAXES_LEFT, + &ME_MOUSEADVANCED_DAXES_RIGHT, +}; + +static const char *MenuJoystickHatDirections[] = { "Up", "Right", "Down", "Left", }; + +static char const *MEOSN_JOYSTICKAXIS_ANALOG[] = { " -None-", "Turning", "Strafing", "Looking", "Moving", }; +static int32_t MEOSV_JOYSTICKAXIS_ANALOG[] = { -1, analog_turning, analog_strafing, analog_lookingupanddown, analog_moving, }; +static MenuOptionSet_t MEOS_JOYSTICKAXIS_ANALOG = MAKE_MENUOPTIONSET( MEOSN_JOYSTICKAXIS_ANALOG, MEOSV_JOYSTICKAXIS_ANALOG, 0x0 ); +static MenuOption_t MEO_JOYSTICKAXIS_ANALOG = MAKE_MENUOPTION( &MF_Bluefont, &MEOS_JOYSTICKAXIS_ANALOG, NULL ); +static MenuEntry_t ME_JOYSTICKAXIS_ANALOG = MAKE_MENUENTRY( "Analog", &MF_Redfont, &MEF_BigSliders, &MEO_JOYSTICKAXIS_ANALOG, Option ); +static MenuRangeInt32_t MEO_JOYSTICKAXIS_SCALE = MAKE_MENURANGE( NULL, &MF_Bluefont, -262144, 262144, 65536, 65, 3 ); +static MenuEntry_t ME_JOYSTICKAXIS_SCALE = MAKE_MENUENTRY( "Scale", &MF_Redfont, &MEF_BigSliders, &MEO_JOYSTICKAXIS_SCALE, RangeInt32 ); +static MenuRangeInt32_t MEO_JOYSTICKAXIS_DEAD = MAKE_MENURANGE( NULL, &MF_Bluefont, 0, 1000000, 0, 33, 2 ); +static MenuEntry_t ME_JOYSTICKAXIS_DEAD = MAKE_MENUENTRY( "Dead Zone", &MF_Redfont, &MEF_BigSliders, &MEO_JOYSTICKAXIS_DEAD, RangeInt32 ); +static MenuRangeInt32_t MEO_JOYSTICKAXIS_SATU = MAKE_MENURANGE( NULL, &MF_Bluefont, 0, 1000000, 0, 33, 2 ); +static MenuEntry_t ME_JOYSTICKAXIS_SATU = MAKE_MENUENTRY( "Saturation", &MF_Redfont, &MEF_BigSliders, &MEO_JOYSTICKAXIS_SATU, RangeInt32 ); + +static MenuOption_t MEO_JOYSTICKAXIS_DIGITALNEGATIVE = MAKE_MENUOPTION( &MF_Minifont, &MEOS_Gamefuncs, NULL ); +static MenuEntry_t ME_JOYSTICKAXIS_DIGITALNEGATIVE = MAKE_MENUENTRY( "Digital -", &MF_BluefontRed, &MEF_BigSliders, &MEO_JOYSTICKAXIS_DIGITALNEGATIVE, Option ); +static MenuOption_t MEO_JOYSTICKAXIS_DIGITALPOSITIVE = MAKE_MENUOPTION( &MF_Minifont, &MEOS_Gamefuncs, NULL ); +static MenuEntry_t ME_JOYSTICKAXIS_DIGITALPOSITIVE = MAKE_MENUENTRY( "Digital +", &MF_BluefontRed, &MEF_BigSliders, &MEO_JOYSTICKAXIS_DIGITALPOSITIVE, Option ); + +static MenuEntry_t *MEL_JOYSTICKAXIS[] = { + &ME_JOYSTICKAXIS_ANALOG, + &ME_JOYSTICKAXIS_SCALE, + &ME_JOYSTICKAXIS_DEAD, + &ME_JOYSTICKAXIS_SATU, + &ME_Space8_Redfont, + &ME_JOYSTICKAXIS_DIGITALNEGATIVE, + &ME_JOYSTICKAXIS_DIGITALPOSITIVE, +}; + +static MenuEntry_t *MEL_INTERNAL_JOYSTICKAXIS_DIGITAL[] = { + &ME_JOYSTICKAXIS_DIGITALNEGATIVE, + &ME_JOYSTICKAXIS_DIGITALPOSITIVE, +}; + +#ifdef USE_OPENGL +static MenuOption_t MEO_RENDERERSETUP_HIGHTILE = MAKE_MENUOPTION( &MF_Bluefont, &MEOS_NoYes, &usehightile ); +static MenuEntry_t ME_RENDERERSETUP_HIGHTILE = MAKE_MENUENTRY( "True color textures:", &MF_BluefontRed, &MEF_SmallOptions, &MEO_RENDERERSETUP_HIGHTILE, Option ); + +static char const *MEOSN_RENDERERSETUP_TEXQUALITY [] = { "Full", "Half", "Barf", }; +static MenuOptionSet_t MEOS_RENDERERSETUP_TEXQUALITY = MAKE_MENUOPTIONSET(MEOSN_RENDERERSETUP_TEXQUALITY, NULL, 0x2); +static MenuOption_t MEO_RENDERERSETUP_TEXQUALITY = MAKE_MENUOPTION(&MF_Bluefont, &MEOS_RENDERERSETUP_TEXQUALITY, &r_downsize); +static MenuEntry_t ME_RENDERERSETUP_TEXQUALITY = MAKE_MENUENTRY("GL texture quality:", &MF_BluefontRed, &MEF_SmallOptions, &MEO_RENDERERSETUP_TEXQUALITY, Option); + + +static MenuOption_t MEO_RENDERERSETUP_PRECACHE = MAKE_MENUOPTION( &MF_Bluefont, &MEOS_OffOn, &ud.config.useprecache ); +static MenuEntry_t ME_RENDERERSETUP_PRECACHE = MAKE_MENUENTRY( "Pre-load map textures:", &MF_BluefontRed, &MEF_SmallOptions, &MEO_RENDERERSETUP_PRECACHE, Option ); +# ifndef EDUKE32_GLES +static char const *MEOSN_RENDERERSETUP_TEXCACHE[] = { "Off", "On", "Compr.", }; +static MenuOptionSet_t MEOS_RENDERERSETUP_TEXCACHE = MAKE_MENUOPTIONSET( MEOSN_RENDERERSETUP_TEXCACHE, NULL, 0x2 ); +static MenuOption_t MEO_RENDERERSETUP_TEXCACHE = MAKE_MENUOPTION( &MF_Bluefont, &MEOS_RENDERERSETUP_TEXCACHE, &glusetexcache ); +static MenuEntry_t ME_RENDERERSETUP_TEXCACHE = MAKE_MENUENTRY( "On-disk texture cache:", &MF_BluefontRed, &MEF_SmallOptions, &MEO_RENDERERSETUP_TEXCACHE, Option ); +# endif +# ifdef USE_GLEXT +static MenuOption_t MEO_RENDERERSETUP_DETAILTEX = MAKE_MENUOPTION( &MF_Bluefont, &MEOS_NoYes, &r_detailmapping ); +static MenuEntry_t ME_RENDERERSETUP_DETAILTEX = MAKE_MENUENTRY( "Detail textures:", &MF_BluefontRed, &MEF_SmallOptions, &MEO_RENDERERSETUP_DETAILTEX, Option ); +static MenuOption_t MEO_RENDERERSETUP_GLOWTEX = MAKE_MENUOPTION(&MF_Bluefont, &MEOS_NoYes, &r_glowmapping); +static MenuEntry_t ME_RENDERERSETUP_GLOWTEX = MAKE_MENUENTRY("Glow textures:", &MF_BluefontRed, &MEF_SmallOptions, &MEO_RENDERERSETUP_GLOWTEX, Option); +# endif +static MenuOption_t MEO_RENDERERSETUP_MODELS = MAKE_MENUOPTION( &MF_Bluefont, &MEOS_NoYes, &usemodels ); +static MenuEntry_t ME_RENDERERSETUP_MODELS = MAKE_MENUENTRY( "3D models:", &MF_BluefontRed, &MEF_SmallOptions, &MEO_RENDERERSETUP_MODELS, Option ); +static MenuOption_t MEO_RENDERERSETUP_PALETTEEMULATION = MAKE_MENUOPTION(&MF_Bluefont, &MEOS_NoYes, &r_usetileshades); +static MenuEntry_t ME_RENDERERSETUP_PALETTEEMULATION = MAKE_MENUENTRY("Palette emulation:", &MF_BluefontRed, &MEF_SmallOptions, &MEO_RENDERERSETUP_PALETTEEMULATION, Option); +#endif + +#ifdef POLYMER +static char const *MEOSN_POLYMER_LIGHTS [] = { "Off", "Full", "Map only", }; +static MenuOptionSet_t MEOS_POLYMER_LIGHTS = MAKE_MENUOPTIONSET(MEOSN_POLYMER_LIGHTS, NULL, 0x2); +static MenuOption_t MEO_POLYMER_LIGHTS = MAKE_MENUOPTION(&MF_Bluefont, &MEOS_POLYMER_LIGHTS, &pr_lighting); +static MenuEntry_t ME_POLYMER_LIGHTS = MAKE_MENUENTRY("Dynamic lights:", &MF_BluefontRed, &MEF_SmallOptions, &MEO_POLYMER_LIGHTS, Option); + +static MenuRangeInt32_t MEO_POLYMER_LIGHTPASSES = MAKE_MENURANGE(&r_pr_maxlightpasses, &MF_Bluefont, 1, 10, 1, 10, 1); +static MenuEntry_t ME_POLYMER_LIGHTPASSES = MAKE_MENUENTRY("Lights per surface:", &MF_BluefontRed, &MEF_SmallOptions, &MEO_POLYMER_LIGHTPASSES, RangeInt32); + +static MenuOption_t MEO_POLYMER_SHADOWS = MAKE_MENUOPTION(&MF_Bluefont, &MEOS_OffOn, &pr_shadows); +static MenuEntry_t ME_POLYMER_SHADOWS = MAKE_MENUENTRY("Dynamic shadows:", &MF_BluefontRed, &MEF_SmallOptions, &MEO_POLYMER_SHADOWS, Option); + +static MenuRangeInt32_t MEO_POLYMER_SHADOWCOUNT = MAKE_MENURANGE(&pr_shadowcount, &MF_Bluefont, 1, 10, 1, 10, 1); +static MenuEntry_t ME_POLYMER_SHADOWCOUNT = MAKE_MENUENTRY("Shadows per surface:", &MF_BluefontRed, &MEF_SmallOptions, &MEO_POLYMER_SHADOWCOUNT, RangeInt32); + +static MenuOption_t MEO_POLYMER_PALETTEEMULATION = MAKE_MENUOPTION(&MF_Bluefont, &MEOS_NoYes, &pr_artmapping); +static MenuEntry_t ME_POLYMER_PALETTEEMULATION = MAKE_MENUENTRY("Palette emulation:", &MF_BluefontRed, &MEF_SmallOptions, &MEO_POLYMER_PALETTEEMULATION, Option); + +#endif + +#ifdef USE_OPENGL +static MenuEntry_t *MEL_RENDERERSETUP_POLYMOST[] = { + &ME_RENDERERSETUP_HIGHTILE, + &ME_RENDERERSETUP_TEXQUALITY, + &ME_RENDERERSETUP_PRECACHE, +# ifndef EDUKE32_GLES + &ME_RENDERERSETUP_TEXCACHE, +# endif +# ifdef USE_GLEXT + &ME_RENDERERSETUP_DETAILTEX, + &ME_RENDERERSETUP_GLOWTEX, +# endif + &ME_Space4_Bluefont, + &ME_RENDERERSETUP_MODELS, + &ME_RENDERERSETUP_PALETTEEMULATION, +}; + +#ifdef POLYMER +static MenuEntry_t *MEL_RENDERERSETUP_POLYMER [] = { + &ME_RENDERERSETUP_HIGHTILE, + &ME_RENDERERSETUP_TEXQUALITY, + &ME_RENDERERSETUP_PRECACHE, +# ifndef EDUKE32_GLES + &ME_RENDERERSETUP_TEXCACHE, +# endif +# ifdef USE_GLEXT + &ME_RENDERERSETUP_DETAILTEX, + &ME_RENDERERSETUP_GLOWTEX, + &ME_POLYMER_PALETTEEMULATION, +# endif + &ME_Space4_Bluefont, + &ME_RENDERERSETUP_MODELS, + &ME_Space4_Bluefont, + &ME_POLYMER_LIGHTS, + &ME_POLYMER_LIGHTPASSES, + &ME_POLYMER_SHADOWS, + &ME_POLYMER_SHADOWCOUNT, +}; +#endif +#endif + +#ifdef EDUKE32_ANDROID_MENU +static MenuRangeFloat_t MEO_COLCORR_GAMMA = MAKE_MENURANGE( &vid_gamma, &MF_Bluefont, 1.f, 2.5f, 0.f, 39, 1 ); +#else +static MenuRangeFloat_t MEO_COLCORR_GAMMA = MAKE_MENURANGE( &vid_gamma, &MF_Bluefont, 0.3f, 4.f, 0.f, 38, 1 ); +#endif +static MenuEntry_t ME_COLCORR_GAMMA = MAKE_MENUENTRY( "Gamma:", &MF_Redfont, &MEF_ColorCorrect, &MEO_COLCORR_GAMMA, RangeFloat ); +static MenuRangeFloat_t MEO_COLCORR_CONTRAST = MAKE_MENURANGE( &vid_contrast, &MF_Bluefont, 0.1f, 2.7f, 0.f, 53, 1 ); +static MenuEntry_t ME_COLCORR_CONTRAST = MAKE_MENUENTRY( "Contrast:", &MF_Redfont, &MEF_ColorCorrect, &MEO_COLCORR_CONTRAST, RangeFloat ); +static MenuRangeFloat_t MEO_COLCORR_BRIGHTNESS = MAKE_MENURANGE( &vid_brightness, &MF_Bluefont, -0.8f, 0.8f, 0.f, 33, 1 ); +static MenuEntry_t ME_COLCORR_BRIGHTNESS = MAKE_MENUENTRY( "Brightness:", &MF_Redfont, &MEF_ColorCorrect, &MEO_COLCORR_BRIGHTNESS, RangeFloat ); +static MenuEntry_t ME_COLCORR_RESET = MAKE_MENUENTRY( "Reset To Defaults", &MF_Redfont, &MEF_ColorCorrect, &MEO_NULL, Link ); +#ifdef EDUKE32_ANDROID_MENU +#define MINVIS 1.f +#else +#define MINVIS 0.125f +#endif +#ifndef EDUKE32_SIMPLE_MENU +static MenuRangeFloat_t MEO_COLCORR_AMBIENT = MAKE_MENURANGE( &r_ambientlight, &MF_Bluefont, MINVIS, 4.f, 0.f, 32, 1 ); +static MenuEntry_t ME_COLCORR_AMBIENT = MAKE_MENUENTRY( "Visibility:", &MF_Redfont, &MEF_ColorCorrect, &MEO_COLCORR_AMBIENT, RangeFloat ); +#endif +static MenuEntry_t *MEL_COLCORR[] = { + &ME_COLCORR_GAMMA, +#ifndef EDUKE32_ANDROID_MENU + &ME_COLCORR_CONTRAST, + &ME_COLCORR_BRIGHTNESS, +#endif +#ifndef EDUKE32_SIMPLE_MENU + &ME_COLCORR_AMBIENT, +#endif + &ME_Space8_Redfont, + &ME_COLCORR_RESET, +}; + +static MenuEntry_t *MEL_SCREENSETUP[] = { +#ifndef EDUKE32_SIMPLE_MENU + &ME_SCREENSETUP_SCREENSIZE, +#endif + + &ME_SCREENSETUP_SBARSIZE, + + &ME_SCREENSETUP_CROSSHAIR, + &ME_SCREENSETUP_CROSSHAIRSIZE, + + &ME_SCREENSETUP_LEVELSTATS, + &ME_SCREENSETUP_TEXTSIZE, + + &ME_SCREENSETUP_SHOWPICKUPMESSAGES, +}; + +// Save and load will be filled in before every viewing of the save/load screen. +static MenuLink_t MEO_LOAD = { MENU_LOADVERIFY, MA_None, }; +static MenuEntry_t ME_LOAD_TEMPLATE = MAKE_MENUENTRY( NULL, &MF_MinifontSave, &MEF_LoadSave, &MEO_LOAD, Link ); +static MenuEntry_t ME_LOAD_EMPTY = MAKE_MENUENTRY( NULL, &MF_MinifontSave, &MEF_LoadSave, nullptr, Dummy ); +static MenuEntry_t *ME_LOAD; +static MenuEntry_t **MEL_LOAD; + +static char const s_NewSaveGame[] = "(New Save Game)"; +static MenuString_t MEO_SAVE_TEMPLATE = MAKE_MENUSTRING( NULL, &MF_MinifontSave, MAXSAVEGAMENAME, 0 ); +static MenuString_t MEO_SAVE_NEW = MAKE_MENUSTRING( NULL, &MF_MinifontSave, MAXSAVEGAMENAME, 0 ); +static MenuString_t *MEO_SAVE; +static MenuEntry_t ME_SAVE_TEMPLATE = MAKE_MENUENTRY( NULL, &MF_MinifontSave, &MEF_LoadSave, &MEO_SAVE_TEMPLATE, String ); +static MenuEntry_t ME_SAVE_NEW = MAKE_MENUENTRY( s_NewSaveGame, &MF_MinifontSave, &MEF_LoadSave, &MEO_SAVE_NEW, String ); +static MenuEntry_t *ME_SAVE; +static MenuEntry_t **MEL_SAVE; + +static int32_t soundrate, soundvoices, musicbackend; +static MenuOption_t MEO_SOUND = MAKE_MENUOPTION( &MF_Redfont, &MEOS_OffOn, &ud.config.SoundToggle ); +static MenuEntry_t ME_SOUND = MAKE_MENUENTRY( "Sound:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_SOUND, Option ); + +static MenuOption_t MEO_SOUND_MUSIC = MAKE_MENUOPTION( &MF_Redfont, &MEOS_OffOn, &ud.config.MusicToggle ); +static MenuEntry_t ME_SOUND_MUSIC = MAKE_MENUENTRY( "Music:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_SOUND_MUSIC, Option ); + +static MenuRangeInt32_t MEO_SOUND_VOLUME_MASTER = MAKE_MENURANGE( &ud.config.MasterVolume, &MF_Redfont, 0, 255, 0, 33, 2 ); +static MenuEntry_t ME_SOUND_VOLUME_MASTER = MAKE_MENUENTRY( "Volume:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_SOUND_VOLUME_MASTER, RangeInt32 ); + +static MenuRangeInt32_t MEO_SOUND_VOLUME_EFFECTS = MAKE_MENURANGE( &ud.config.FXVolume, &MF_Redfont, 0, 255, 0, 33, 2 ); +static MenuEntry_t ME_SOUND_VOLUME_EFFECTS = MAKE_MENUENTRY( "Effects:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_SOUND_VOLUME_EFFECTS, RangeInt32 ); + +static MenuRangeInt32_t MEO_SOUND_VOLUME_MUSIC = MAKE_MENURANGE( &ud.config.MusicVolume, &MF_Redfont, 0, 255, 0, 33, 2 ); +static MenuEntry_t ME_SOUND_VOLUME_MUSIC = MAKE_MENUENTRY( "Music:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_SOUND_VOLUME_MUSIC, RangeInt32 ); + +static MenuOption_t MEO_SOUND_DUKETALK = MAKE_MENUOPTION(&MF_Redfont, &MEOS_NoYes, NULL); +#ifndef EDUKE32_STANDALONE +static MenuEntry_t ME_SOUND_DUKETALK = MAKE_MENUENTRY( "Duke talk:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_SOUND_DUKETALK, Option ); +#else +static MenuEntry_t ME_SOUND_DUKETALK = MAKE_MENUENTRY("Player speech:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_SOUND_DUKETALK, Option); +#endif + +static char const *MEOSN_SOUND_SAMPLINGRATE[] = { "22050Hz", "44100Hz", "48000Hz", }; +static int32_t MEOSV_SOUND_SAMPLINGRATE[] = { 22050, 44100, 48000, }; +static MenuOptionSet_t MEOS_SOUND_SAMPLINGRATE = MAKE_MENUOPTIONSET( MEOSN_SOUND_SAMPLINGRATE, MEOSV_SOUND_SAMPLINGRATE, 0x3 ); +static MenuOption_t MEO_SOUND_SAMPLINGRATE = MAKE_MENUOPTION( &MF_Redfont, &MEOS_SOUND_SAMPLINGRATE, &soundrate ); +static MenuEntry_t ME_SOUND_SAMPLINGRATE = MAKE_MENUENTRY( "Sample rate:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_SOUND_SAMPLINGRATE, Option ); + +#ifndef EDUKE32_SIMPLE_MENU +static MenuRangeInt32_t MEO_SOUND_NUMVOICES = MAKE_MENURANGE( &soundvoices, &MF_Redfont, 16, 256, 0, 16, 1 ); +static MenuEntry_t ME_SOUND_NUMVOICES = MAKE_MENUENTRY( "Voices:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_SOUND_NUMVOICES, RangeInt32 ); +#endif + +static char const *MEOSN_SOUND_FLUIDSYNTH_BACKEND[] ={ "default", "jack", "alsa", "oss", "pulseaudio", "coreaudio", "dsound", "portaudio", "sndman", "dart" }; +static int32_t MEOSV_SOUND_FLUIDSYNTH_BACKEND[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; +static MenuOptionSet_t MEOS_SOUND_FLUIDSYNTH_BACKEND = MAKE_MENUOPTIONSET( MEOSN_SOUND_FLUIDSYNTH_BACKEND, MEOSV_SOUND_FLUIDSYNTH_BACKEND, 0x3 ); +static MenuOption_t MEO_SOUND_FLUIDSYNTH_BACKEND = MAKE_MENUOPTION( &MF_Redfont, &MEOS_SOUND_FLUIDSYNTH_BACKEND, &musicbackend ); +static MenuEntry_t ME_SOUND_FLUIDSYNTH_BACKEND = MAKE_MENUENTRY( "Backend:", &MF_Redfont, &MEF_BigOptionsRt, &MEO_SOUND_FLUIDSYNTH_BACKEND, Option ); + +static MenuEntry_t ME_SOUND_RESTART = MAKE_MENUENTRY( "Apply Changes", &MF_Redfont, &MEF_BigOptions_Apply, &MEO_NULL, Link ); + +#ifndef EDUKE32_SIMPLE_MENU +static MenuLink_t MEO_ADVSOUND = { MENU_ADVSOUND, MA_Advance, }; +static MenuEntry_t ME_SOUND_ADVSOUND = MAKE_MENUENTRY( "Advanced", &MF_Redfont, &MEF_BigOptionsRt, &MEO_ADVSOUND, Link ); +#endif + +static MenuEntry_t *MEL_SOUND[] = { + &ME_SOUND, + &ME_SOUND_MUSIC, + &ME_SOUND_VOLUME_MASTER, + &ME_SOUND_VOLUME_EFFECTS, + &ME_SOUND_VOLUME_MUSIC, + &ME_SOUND_DUKETALK, +#ifndef EDUKE32_SIMPLE_MENU + &ME_SOUND_ADVSOUND, +#endif +}; + +static MenuEntry_t *MEL_ADVSOUND[] = { + &ME_SOUND_SAMPLINGRATE, + &ME_Space2_Redfont, +#ifndef EDUKE32_SIMPLE_MENU + &ME_SOUND_NUMVOICES, + &ME_Space2_Redfont, +#endif + &ME_SOUND_FLUIDSYNTH_BACKEND, + &ME_SOUND_RESTART, +}; + +MAKE_MENU_TOP_ENTRYLINK( "Player Setup", MEF_CenterMenu, NETWORK_PLAYERSETUP, MENU_PLAYER ); +MAKE_MENU_TOP_ENTRYLINK( "Join Game", MEF_CenterMenu, NETWORK_JOINGAME, MENU_NETJOIN ); +MAKE_MENU_TOP_ENTRYLINK( "Host Game", MEF_CenterMenu, NETWORK_HOSTGAME, MENU_NETHOST ); + +static MenuEntry_t *MEL_NETWORK[] = { + &ME_NETWORK_PLAYERSETUP, + &ME_NETWORK_JOINGAME, + &ME_NETWORK_HOSTGAME, +}; + +static MenuString_t MEO_PLAYER_NAME = MAKE_MENUSTRING( szPlayerName, &MF_Bluefont, MAXPLAYERNAME, 0 ); +static MenuEntry_t ME_PLAYER_NAME = MAKE_MENUENTRY( "Name", &MF_BluefontRed, &MEF_PlayerNarrow, &MEO_PLAYER_NAME, String ); +static char const *MEOSN_PLAYER_COLOR[] = { "Auto", "Blue", "Red", "Green", "Gray", "Dark gray", "Dark green", "Brown", "Dark blue", "Bright red", "Yellow", }; +static int32_t MEOSV_PLAYER_COLOR[] = { 0, 9, 10, 11, 12, 13, 14, 15, 16, 21, 23, }; +static MenuOptionSet_t MEOS_PLAYER_COLOR = MAKE_MENUOPTIONSET( MEOSN_PLAYER_COLOR, MEOSV_PLAYER_COLOR, 0x2 ); +static MenuOption_t MEO_PLAYER_COLOR = MAKE_MENUOPTION( &MF_Bluefont, &MEOS_PLAYER_COLOR, &ud.color ); +static MenuEntry_t ME_PLAYER_COLOR = MAKE_MENUENTRY( "Color", &MF_BluefontRed, &MEF_PlayerNarrow, &MEO_PLAYER_COLOR, Option ); +static char const *MEOSN_PLAYER_TEAM[] = { "Blue", "Red", "Green", "Gray", }; +static MenuOptionSet_t MEOS_PLAYER_TEAM = MAKE_MENUOPTIONSET( MEOSN_PLAYER_TEAM, NULL, 0x2 ); +static MenuOption_t MEO_PLAYER_TEAM = MAKE_MENUOPTION( &MF_Bluefont, &MEOS_PLAYER_TEAM, &ud.team ); +static MenuEntry_t ME_PLAYER_TEAM = MAKE_MENUENTRY( "Team", &MF_BluefontRed, &MEF_PlayerNarrow, &MEO_PLAYER_TEAM, Option ); +#ifndef EDUKE32_SIMPLE_MENU +static MenuLink_t MEO_PLAYER_MACROS = { MENU_MACROS, MA_Advance, }; +static MenuEntry_t ME_PLAYER_MACROS = MAKE_MENUENTRY( "Multiplayer macros", &MF_BluefontRed, &MEF_SmallOptions, &MEO_PLAYER_MACROS, Link ); +#endif + +static MenuEntry_t *MEL_PLAYER[] = { + &ME_PLAYER_NAME, + &ME_Space4_Bluefont, + &ME_PLAYER_COLOR, + &ME_Space4_Bluefont, + &ME_PLAYER_TEAM, +#ifndef EDUKE32_SIMPLE_MENU + &ME_Space8_Bluefont, + &ME_PLAYER_MACROS, +#endif +}; + +static MenuString_t MEO_MACROS_TEMPLATE = MAKE_MENUSTRING( NULL, &MF_Bluefont, MAXRIDECULELENGTH, 0 ); +static MenuString_t MEO_MACROS[10]; +static MenuEntry_t ME_MACROS_TEMPLATE = MAKE_MENUENTRY( NULL, &MF_Bluefont, &MEF_Macros, &MEO_MACROS_TEMPLATE, String ); +static MenuEntry_t ME_MACROS[MAXRIDECULE]; +static MenuEntry_t *MEL_MACROS[MAXRIDECULE]; + +#ifndef EDUKE32_SIMPLE_MENU +static char const *MenuUserMap = "User Map"; +#endif +static char const *MenuSkillNone = "None"; + +static char const *MEOSN_NetGametypes[MAXGAMETYPES]; +static char const *MEOSN_NetEpisodes[MAXVOLUMES+1]; +static int32_t MEOSV_NetEpisodes[MAXVOLUMES+1]; +static char const *MEOSN_NetLevels[MAXVOLUMES][MAXLEVELS]; +static char const *MEOSN_NetSkills[MAXSKILLS+1]; + +static MenuLink_t MEO_NETHOST_OPTIONS = { MENU_NETOPTIONS, MA_Advance, }; +static MenuEntry_t ME_NETHOST_OPTIONS = MAKE_MENUENTRY( "Game Options", &MF_Redfont, &MEF_VideoSetup, &MEO_NETHOST_OPTIONS, Link ); +static MenuEntry_t ME_NETHOST_LAUNCH = MAKE_MENUENTRY( "Launch Game", &MF_Redfont, &MEF_VideoSetup, &MEO_NULL, Link ); + +static MenuEntry_t *MEL_NETHOST[] = { + &ME_NETHOST_OPTIONS, + &ME_NETHOST_LAUNCH, +}; + +static MenuOptionSet_t MEOS_NETOPTIONS_GAMETYPE = MAKE_MENUOPTIONSET( MEOSN_NetGametypes, NULL, 0x0 ); +static MenuOption_t MEO_NETOPTIONS_GAMETYPE = MAKE_MENUOPTION( &MF_Bluefont, &MEOS_NETOPTIONS_GAMETYPE, &ud.m_coop ); +static MenuEntry_t ME_NETOPTIONS_GAMETYPE = MAKE_MENUENTRY( "Game Type", &MF_Redfont, &MEF_NetSetup, &MEO_NETOPTIONS_GAMETYPE, Option ); +static MenuOptionSet_t MEOS_NETOPTIONS_EPISODE = MAKE_MENUOPTIONSET( MEOSN_NetEpisodes, MEOSV_NetEpisodes, 0x0 ); +static int32_t NetEpisode; +static MenuOption_t MEO_NETOPTIONS_EPISODE = MAKE_MENUOPTION( &MF_Bluefont, &MEOS_NETOPTIONS_EPISODE, &NetEpisode ); +static MenuEntry_t ME_NETOPTIONS_EPISODE = MAKE_MENUENTRY( "Episode", &MF_Redfont, &MEF_NetSetup, &MEO_NETOPTIONS_EPISODE, Option ); +static MenuOptionSet_t MEOS_NETOPTIONS_LEVEL_TEMPLATE = MAKE_MENUOPTIONSETNULL; +static MenuOptionSet_t MEOS_NETOPTIONS_LEVEL[MAXVOLUMES]; +static MenuOption_t MEO_NETOPTIONS_LEVEL = MAKE_MENUOPTION( &MF_Bluefont, NULL, &ud.m_level_number ); +static MenuEntry_t ME_NETOPTIONS_LEVEL = MAKE_MENUENTRY( "Level", &MF_Redfont, &MEF_NetSetup, &MEO_NETOPTIONS_LEVEL, Option ); +static MenuLink_t MEO_NETOPTIONS_USERMAP = { MENU_NETUSERMAP, MA_Advance, }; +static MenuEntry_t ME_NETOPTIONS_USERMAP = MAKE_MENUENTRY( "User Map", &MF_Redfont, &MEF_NetSetup, &MEO_NETOPTIONS_USERMAP, Link ); +static MenuOptionSet_t MEOS_NETOPTIONS_MONSTERS = MAKE_MENUOPTIONSET( MEOSN_NetSkills, NULL, 0x0 ); +static MenuOption_t MEO_NETOPTIONS_MONSTERS = MAKE_MENUOPTION( &MF_Bluefont, &MEOS_NETOPTIONS_MONSTERS, NULL ); +static MenuEntry_t ME_NETOPTIONS_MONSTERS = MAKE_MENUENTRY( "Monsters", &MF_Redfont, &MEF_NetSetup, &MEO_NETOPTIONS_MONSTERS, Option ); +static MenuOption_t MEO_NETOPTIONS_MARKERS = MAKE_MENUOPTION( &MF_Bluefont, &MEOS_OffOn, &ud.m_marker ); +static MenuEntry_t ME_NETOPTIONS_MARKERS = MAKE_MENUENTRY( "Markers", &MF_Redfont, &MEF_NetSetup, &MEO_NETOPTIONS_MARKERS, Option ); +static MenuOption_t MEO_NETOPTIONS_MAPEXITS = MAKE_MENUOPTION( &MF_Bluefont, &MEOS_OnOff, &ud.m_noexits ); +static MenuEntry_t ME_NETOPTIONS_MAPEXITS = MAKE_MENUENTRY( "Map Exits", &MF_Redfont, &MEF_NetSetup, &MEO_NETOPTIONS_MAPEXITS, Option ); +static MenuOption_t MEO_NETOPTIONS_FRFIRE = MAKE_MENUOPTION( &MF_Bluefont, &MEOS_OffOn, &ud.m_ffire ); +static MenuEntry_t ME_NETOPTIONS_FRFIRE = MAKE_MENUENTRY( "Fr. Fire", &MF_Redfont, &MEF_NetSetup, &MEO_NETOPTIONS_FRFIRE, Option ); +static MenuEntry_t ME_NETOPTIONS_ACCEPT = MAKE_MENUENTRY( "Accept", &MF_Redfont, &MEF_NetSetup_Confirm, &MEO_NULL, Link ); + +static MenuEntry_t *MEL_NETOPTIONS[] = { + &ME_NETOPTIONS_GAMETYPE, + &ME_NETOPTIONS_EPISODE, + &ME_NETOPTIONS_LEVEL, + &ME_NETOPTIONS_MONSTERS, + &ME_NETOPTIONS_MARKERS, + &ME_NETOPTIONS_MAPEXITS, + &ME_NETOPTIONS_ACCEPT, +}; + +static char MenuServer[BMAX_PATH] = "localhost"; +static MenuString_t MEO_NETJOIN_SERVER = MAKE_MENUSTRING( MenuServer, &MF_Bluefont, BMAX_PATH, 0 ); +static MenuEntry_t ME_NETJOIN_SERVER = MAKE_MENUENTRY( "Server", &MF_Redfont, &MEF_VideoSetup, &MEO_NETJOIN_SERVER, String ); +#define MAXPORTSTRINGLENGTH 6 // unsigned 16-bit integer +static char MenuPort[MAXPORTSTRINGLENGTH] = "19014"; +static MenuString_t MEO_NETJOIN_PORT = MAKE_MENUSTRING( MenuPort, &MF_Bluefont, MAXPORTSTRINGLENGTH, INPUT_NUMERIC ); +static MenuEntry_t ME_NETJOIN_PORT = MAKE_MENUENTRY( "Port", &MF_Redfont, &MEF_VideoSetup, &MEO_NETJOIN_PORT, String ); +static MenuEntry_t ME_NETJOIN_CONNECT = MAKE_MENUENTRY( "Connect", &MF_Redfont, &MEF_VideoSetup_Apply, &MEO_NULL, Link ); + +static MenuEntry_t *MEL_NETJOIN[] = { + &ME_NETJOIN_SERVER, + &ME_NETJOIN_PORT, + &ME_NETJOIN_CONNECT, +}; + + +#define NoTitle NULL + +#define MAKE_MENUMENU(Title, Format, Entries) { Title, Format, Entries, ARRAY_SIZE(Entries), 0, 0, 0 } +#define MAKE_MENUMENU_CUSTOMSIZE(Title, Format, Entries) { Title, Format, Entries, 0, 0, 0, 0 } + +static MenuMenu_t M_MAIN = MAKE_MENUMENU( NoTitle, &MMF_Top_Main, MEL_MAIN ); +static MenuMenu_t M_MAIN_INGAME = MAKE_MENUMENU( NoTitle, &MMF_Top_Main, MEL_MAIN_INGAME ); +static MenuMenu_t M_EPISODE = MAKE_MENUMENU( "Select An Episode", &MMF_Top_Episode, MEL_EPISODE ); +static MenuMenu_t M_SKILL = MAKE_MENUMENU( "Select Skill", &MMF_Top_Skill, MEL_SKILL ); +#ifndef EDUKE32_SIMPLE_MENU +static MenuMenu_t M_GAMESETUP = MAKE_MENUMENU( "Game Setup", &MMF_BigOptions, MEL_GAMESETUP ); +#endif +static MenuMenu_t M_OPTIONS = MAKE_MENUMENU( s_Options, &MMF_Top_Options, MEL_OPTIONS ); +static MenuMenu_t M_VIDEOSETUP = MAKE_MENUMENU( "Video Mode", &MMF_BigOptions, MEL_VIDEOSETUP ); +static MenuMenu_t M_KEYBOARDSETUP = MAKE_MENUMENU( "Keyboard Setup", &MMF_Top_Options, MEL_KEYBOARDSETUP ); +static MenuMenu_t M_CONTROLS = MAKE_MENUMENU( "Control Setup", &MMF_Top_Options, MEL_CONTROLS ); +static MenuMenu_t M_CHEATS = MAKE_MENUMENU( "Cheats", &MMF_SmallOptions, MEL_CHEATS ); +static MenuMenu_t M_MOUSESETUP = MAKE_MENUMENU( "Mouse Setup", &MMF_BigOptions, MEL_MOUSESETUP ); +#ifdef EDUKE32_ANDROID_MENU +static MenuMenu_t M_TOUCHSETUP = MAKE_MENUMENU( "Touch Setup", &MMF_Top_Options, MEL_TOUCHSETUP ); +static MenuMenu_t M_TOUCHSENS = MAKE_MENUMENU( "Sensitivity", &MMF_BigOptions, MEL_TOUCHSENS); +static MenuPanel_t M_TOUCHBUTTONS = { "Button Setup", MENU_TOUCHSETUP, MA_Return, MENU_TOUCHSETUP, MA_Advance, }; +#endif +static MenuMenu_t M_JOYSTICKSETUP = MAKE_MENUMENU( "Joystick Setup", &MMF_Top_Joystick_Network, MEL_JOYSTICKSETUP ); +static MenuMenu_t M_JOYSTICKBTNS = MAKE_MENUMENU( "Joystick Buttons", &MMF_MouseJoySetupBtns, MEL_JOYSTICKBTNS ); +static MenuMenu_t M_JOYSTICKAXES = MAKE_MENUMENU( "Joystick Axes", &MMF_BigSliders, MEL_JOYSTICKAXES ); +static MenuMenu_t M_KEYBOARDKEYS = MAKE_MENUMENU( "Key Configuration", &MMF_KeyboardSetupFuncs, MEL_KEYBOARDSETUPFUNCS ); +static MenuMenu_t M_MOUSEBTNS = MAKE_MENUMENU( "Mouse Buttons", &MMF_MouseJoySetupBtns, MEL_MOUSESETUPBTNS ); +static MenuMenu_t M_MOUSEADVANCED = MAKE_MENUMENU( "Advanced Mouse", &MMF_BigSliders, MEL_MOUSEADVANCED ); +static MenuMenu_t M_JOYSTICKAXIS = MAKE_MENUMENU( NULL, &MMF_BigSliders, MEL_JOYSTICKAXIS ); +#ifdef USE_OPENGL +static MenuMenu_t M_RENDERERSETUP_POLYMOST = MAKE_MENUMENU( "Polymost Setup", &MMF_SmallOptions, MEL_RENDERERSETUP_POLYMOST ); +# ifdef POLYMER +static MenuMenu_t M_RENDERERSETUP_POLYMER = MAKE_MENUMENU("Polymer Setup", &MMF_SmallOptions, MEL_RENDERERSETUP_POLYMER ); +# endif +#endif +static MenuMenu_t M_COLCORR = MAKE_MENUMENU( "Color Correction", &MMF_ColorCorrect, MEL_COLCORR ); +static MenuMenu_t M_SCREENSETUP = MAKE_MENUMENU( "HUD Setup", &MMF_BigOptions, MEL_SCREENSETUP ); +static MenuMenu_t M_DISPLAYSETUP = MAKE_MENUMENU( "Display Setup", &MMF_BigOptions, MEL_DISPLAYSETUP ); +static MenuMenu_t M_LOAD = MAKE_MENUMENU_CUSTOMSIZE( s_LoadGame, &MMF_LoadSave, MEL_LOAD ); +static MenuMenu_t M_SAVE = MAKE_MENUMENU_CUSTOMSIZE( s_SaveGame, &MMF_LoadSave, MEL_SAVE ); +static MenuMenu_t M_SOUND = MAKE_MENUMENU( "Sound Setup", &MMF_BigOptions, MEL_SOUND ); +static MenuMenu_t M_ADVSOUND = MAKE_MENUMENU( "Advanced Sound", &MMF_BigOptions, MEL_ADVSOUND ); +static MenuMenu_t M_NETWORK = MAKE_MENUMENU( "Network Game", &MMF_Top_Joystick_Network, MEL_NETWORK ); +static MenuMenu_t M_PLAYER = MAKE_MENUMENU( "Player Setup", &MMF_SmallOptions, MEL_PLAYER ); +static MenuMenu_t M_MACROS = MAKE_MENUMENU( "Multiplayer Macros", &MMF_Macros, MEL_MACROS ); +static MenuMenu_t M_NETHOST = MAKE_MENUMENU( "Host Network Game", &MMF_SmallOptionsNarrow, MEL_NETHOST ); +static MenuMenu_t M_NETOPTIONS = MAKE_MENUMENU( "Net Game Options", &MMF_NetSetup, MEL_NETOPTIONS ); +static MenuMenu_t M_NETJOIN = MAKE_MENUMENU( "Join Network Game", &MMF_SmallOptionsNarrow, MEL_NETJOIN ); + +#ifdef EDUKE32_SIMPLE_MENU +static MenuPanel_t M_STORY = { NoTitle, MENU_STORY, MA_Return, MENU_STORY, MA_Advance, }; +#else +static MenuPanel_t M_STORY = { NoTitle, MENU_F1HELP, MA_Return, MENU_F1HELP, MA_Advance, }; +#endif + +static MenuPanel_t M_F1HELP = { NoTitle, MENU_STORY, MA_Return, MENU_STORY, MA_Advance, }; +static MenuPanel_t M_CREDITS = { NoTitle, MENU_CREDITS5, MA_Return, MENU_CREDITS2, MA_Advance, }; +static MenuPanel_t M_CREDITS2 = { NoTitle, MENU_CREDITS, MA_Return, MENU_CREDITS3, MA_Advance, }; +static MenuPanel_t M_CREDITS3 = { NoTitle, MENU_CREDITS2, MA_Return, MENU_CREDITS4, MA_Advance, }; +static MenuPanel_t M_CREDITS4 = { "About " APPNAME, MENU_CREDITS3, MA_Return, MENU_CREDITS5, MA_Advance, }; +static MenuPanel_t M_CREDITS5 = { "About " APPNAME, MENU_CREDITS4, MA_Return, MENU_CREDITS, MA_Advance, }; + +#define CURSOR_CENTER_2LINE { MENU_MARGIN_CENTER<<16, 120<<16, } +#define CURSOR_CENTER_3LINE { MENU_MARGIN_CENTER<<16, 129<<16, } +#define CURSOR_BOTTOMRIGHT { 304<<16, 186<<16, } + +static MenuVerify_t M_QUIT = { CURSOR_CENTER_2LINE, MENU_CLOSE, MA_None, }; +static MenuVerify_t M_QUITTOTITLE = { CURSOR_CENTER_2LINE, MENU_CLOSE, MA_None, }; +static MenuVerify_t M_LOADVERIFY = { CURSOR_CENTER_3LINE, MENU_CLOSE, MA_None, }; +static MenuVerify_t M_NEWVERIFY = { CURSOR_CENTER_2LINE, MENU_EPISODE, MA_Advance, }; +static MenuVerify_t M_SAVEVERIFY = { CURSOR_CENTER_2LINE, MENU_SAVE, MA_None, }; +static MenuVerify_t M_RESETPLAYER = { CURSOR_CENTER_3LINE, MENU_CLOSE, MA_None, }; + +static MenuMessage_t M_NETWAITMASTER = { CURSOR_BOTTOMRIGHT, MENU_NULL, MA_None, }; +static MenuMessage_t M_NETWAITVOTES = { CURSOR_BOTTOMRIGHT, MENU_NULL, MA_None, }; +static MenuMessage_t M_BUYDUKE = { CURSOR_BOTTOMRIGHT, MENU_EPISODE, MA_Return, }; + +static MenuTextForm_t M_ADULTPASSWORD = { NULL, "Enter Password:", MAXPWLOCKOUT, MTF_Password }; +static MenuTextForm_t M_CHEATENTRY = { NULL, "Enter Cheat Code:", MAXCHEATLEN, 0 }; +static MenuTextForm_t M_CHEAT_WARP = { NULL, "Enter Warp #:", 3, 0 }; +static MenuTextForm_t M_CHEAT_SKILL = { NULL, "Enter Skill #:", 1, 0 }; + +#define MAKE_MENUFILESELECT(a, b, c) { a, { &MMF_FileSelectLeft, &MMF_FileSelectRight }, { &MF_Minifont, &MF_MinifontRed }, b, c, { NULL, NULL }, { 0, 0 }, { 3<<16, 3<<16 }, FNLIST_INITIALIZER, 0 } + +static MenuFileSelect_t M_USERMAP = MAKE_MENUFILESELECT( "Select A User Map", "*.map", boardfilename ); + +// MUST be in ascending order of MenuID enum values due to binary search +static Menu_t Menus[] = { + { &M_MAIN, MENU_MAIN, MENU_CLOSE, MA_None, Menu }, + { &M_MAIN_INGAME, MENU_MAIN_INGAME, MENU_CLOSE, MA_None, Menu }, + { &M_EPISODE, MENU_EPISODE, MENU_MAIN, MA_Return, Menu }, + { &M_USERMAP, MENU_USERMAP, MENU_EPISODE, MA_Return, FileSelect }, + { &M_SKILL, MENU_SKILL, MENU_EPISODE, MA_Return, Menu }, +#ifndef EDUKE32_SIMPLE_MENU + { &M_GAMESETUP, MENU_GAMESETUP, MENU_OPTIONS, MA_Return, Menu }, +#endif + { &M_OPTIONS, MENU_OPTIONS, MENU_MAIN, MA_Return, Menu }, + { &M_VIDEOSETUP, MENU_VIDEOSETUP, MENU_DISPLAYSETUP, MA_Return, Menu }, + { &M_KEYBOARDSETUP, MENU_KEYBOARDSETUP, MENU_CONTROLS, MA_Return, Menu }, + { &M_MOUSESETUP, MENU_MOUSESETUP, MENU_CONTROLS, MA_Return, Menu }, + { &M_JOYSTICKSETUP, MENU_JOYSTICKSETUP, MENU_CONTROLS, MA_Return, Menu }, + { &M_JOYSTICKBTNS, MENU_JOYSTICKBTNS, MENU_JOYSTICKSETUP, MA_Return, Menu }, + { &M_JOYSTICKAXES, MENU_JOYSTICKAXES, MENU_JOYSTICKSETUP, MA_Return, Menu }, + { &M_KEYBOARDKEYS, MENU_KEYBOARDKEYS, MENU_KEYBOARDSETUP, MA_Return, Menu }, + { &M_MOUSEBTNS, MENU_MOUSEBTNS, MENU_MOUSESETUP, MA_Return, Menu }, + { &M_MOUSEADVANCED, MENU_MOUSEADVANCED, MENU_MOUSESETUP, MA_Return, Menu }, + { &M_JOYSTICKAXIS, MENU_JOYSTICKAXIS, MENU_JOYSTICKAXES, MA_Return, Menu }, +#ifdef EDUKE32_ANDROID_MENU + { &M_TOUCHSETUP, MENU_TOUCHSETUP, MENU_OPTIONS, MA_Return, Menu }, + { &M_TOUCHSENS, MENU_TOUCHSENS, MENU_TOUCHSETUP, MA_Return, Menu }, + { &M_TOUCHBUTTONS, MENU_TOUCHBUTTONS, MENU_TOUCHSETUP, MA_Return, Panel }, +#endif + { &M_CONTROLS, MENU_CONTROLS, MENU_OPTIONS, MA_Return, Menu }, +#ifdef USE_OPENGL + { &M_RENDERERSETUP_POLYMOST, MENU_POLYMOST, MENU_DISPLAYSETUP, MA_Return, Menu }, +#endif + { &M_COLCORR, MENU_COLCORR, MENU_DISPLAYSETUP, MA_Return, Menu }, + { &M_COLCORR, MENU_COLCORR_INGAME, MENU_CLOSE, MA_Return, Menu }, + { &M_SCREENSETUP, MENU_SCREENSETUP, MENU_DISPLAYSETUP, MA_Return, Menu }, + { &M_DISPLAYSETUP, MENU_DISPLAYSETUP, MENU_OPTIONS, MA_Return, Menu }, +#ifdef POLYMER + { &M_RENDERERSETUP_POLYMER, MENU_POLYMER, MENU_DISPLAYSETUP, MA_Return, Menu }, +#endif + { &M_LOAD, MENU_LOAD, MENU_MAIN, MA_Return, Menu }, + { &M_SAVE, MENU_SAVE, MENU_MAIN, MA_Return, Menu }, + { &M_STORY, MENU_STORY, MENU_MAIN, MA_Return, Panel }, + { &M_F1HELP, MENU_F1HELP, MENU_MAIN, MA_Return, Panel }, + { &M_QUIT, MENU_QUIT, MENU_PREVIOUS, MA_Return, Verify }, + { &M_QUITTOTITLE, MENU_QUITTOTITLE, MENU_PREVIOUS, MA_Return, Verify }, + { &M_QUIT, MENU_QUIT_INGAME, MENU_CLOSE, MA_None, Verify }, + { &M_NETHOST, MENU_NETSETUP, MENU_MAIN, MA_Return, Menu }, + { &M_NETWAITMASTER, MENU_NETWAITMASTER, MENU_MAIN, MA_Return, Message }, + { &M_NETWAITVOTES, MENU_NETWAITVOTES, MENU_MAIN, MA_Return, Message }, + { &M_SOUND, MENU_SOUND, MENU_OPTIONS, MA_Return, Menu }, + { &M_SOUND, MENU_SOUND_INGAME, MENU_CLOSE, MA_Return, Menu }, + { &M_ADVSOUND, MENU_ADVSOUND, MENU_SOUND, MA_Return, Menu }, +#ifdef EDUKE32_SIMPLE_MENU + { &M_CHEATS, MENU_CHEATS, MENU_OPTIONS, MA_Return, Menu }, +#else + { &M_CHEATS, MENU_CHEATS, MENU_GAMESETUP, MA_Return, Menu }, +#endif + { &M_CHEATENTRY, MENU_CHEATENTRY, MENU_CHEATS, MA_None, TextForm }, + { &M_CHEAT_WARP, MENU_CHEAT_WARP, MENU_CHEATS, MA_None, TextForm }, + { &M_CHEAT_SKILL, MENU_CHEAT_SKILL, MENU_CHEATS, MA_None, TextForm }, + { &M_CREDITS, MENU_CREDITS, MENU_MAIN, MA_Return, Panel }, + { &M_CREDITS2, MENU_CREDITS2, MENU_MAIN, MA_Return, Panel }, + { &M_CREDITS3, MENU_CREDITS3, MENU_MAIN, MA_Return, Panel }, + { &M_CREDITS4, MENU_CREDITS4, MENU_MAIN, MA_Return, Panel }, + { &M_CREDITS5, MENU_CREDITS5, MENU_MAIN, MA_Return, Panel }, + { &M_LOADVERIFY, MENU_LOADVERIFY, MENU_LOAD, MA_None, Verify }, + { &M_NEWVERIFY, MENU_NEWVERIFY, MENU_MAIN, MA_Return, Verify }, + { &M_SAVEVERIFY, MENU_SAVEVERIFY, MENU_SAVE, MA_None, Verify }, + { &M_ADULTPASSWORD, MENU_ADULTPASSWORD, MENU_GAMESETUP, MA_None, TextForm }, + { &M_RESETPLAYER, MENU_RESETPLAYER, MENU_CLOSE, MA_None, Verify }, + { &M_BUYDUKE, MENU_BUYDUKE, MENU_EPISODE, MA_Return, Message }, + { &M_NETWORK, MENU_NETWORK, MENU_MAIN, MA_Return, Menu }, + { &M_PLAYER, MENU_PLAYER, MENU_OPTIONS, MA_Return, Menu }, + { &M_MACROS, MENU_MACROS, MENU_PLAYER, MA_Return, Menu }, + { &M_NETHOST, MENU_NETHOST, MENU_NETWORK, MA_Return, Menu }, + { &M_NETOPTIONS, MENU_NETOPTIONS, MENU_NETWORK, MA_Return, Menu }, + { &M_USERMAP, MENU_NETUSERMAP, MENU_NETOPTIONS, MA_Return, FileSelect }, + { &M_NETJOIN, MENU_NETJOIN, MENU_NETWORK, MA_Return, Menu }, +}; + +static CONSTEXPR const size_t numMenus = ARRAY_SIZE(Menus); + +Menu_t *m_currentMenu = &Menus[0]; +static Menu_t *m_previousMenu = &Menus[0]; +static Menu_t * m_parentMenu; + +MenuID_t g_currentMenu; +static MenuID_t g_previousMenu; + +#define MenuMenu_ChangeEntryList(m, el)\ + do {\ + m.entrylist = el;\ + m.numEntries = ARRAY_SIZE(el);\ + } while (0) + +static void MenuEntry_DisableOnCondition(MenuEntry_t * const entry, const int32_t condition) +{ + if (condition) + entry->flags |= MEF_Disabled; + else + entry->flags &= ~MEF_Disabled; +} +static void MenuEntry_LookDisabledOnCondition(MenuEntry_t * const entry, const int32_t condition) +{ + if (condition) + entry->flags |= MEF_LookDisabled; + else + entry->flags &= ~MEF_LookDisabled; +} + +static int32_t M_RunMenu_Menu(Menu_t *cm, MenuMenu_t *menu, MenuEntry_t *currentry, int32_t state, const vec2_t origin, bool actually_draw = true); +static void Menu_EntryFocus(/*MenuEntry_t *entry*/); + +static MenuEntry_t *Menu_AdjustForCurrentEntryAssignment(MenuMenu_t *menu) +{ + MenuEntry_t *currentry = menu->entrylist[menu->currentEntry]; + + Menu_EntryFocus(/*currentry*/); + + if (currentry->ybottom - menu->scrollPos > klabs(menu->format->bottomcutoff)) + menu->scrollPos = currentry->ybottom - klabs(menu->format->bottomcutoff); + else if (currentry->ytop - menu->scrollPos < menu->format->pos.y) + menu->scrollPos = currentry->ytop - menu->format->pos.y; + + return currentry; +} + +static MenuEntry_t *Menu_AdjustForCurrentEntryAssignmentBlind(MenuMenu_t *menu) +{ + M_RunMenu_Menu(nullptr, menu, nullptr, 0, { 0, 0 }, false); + return Menu_AdjustForCurrentEntryAssignment(menu); +} + +/* +This function prepares data after ART and CON have been processed. +It also initializes some data in loops rather than statically at compile time. +*/ +void Menu_Init(void) +{ + int32_t i, j, k; + + // prepare menu fonts + MF_Redfont.tilenum = BIGALPHANUM; + MF_Bluefont.tilenum = MF_BluefontRed.tilenum = MF_BluefontGame.tilenum = STARTALPHANUM; + MF_Minifont.tilenum = MF_MinifontRed.tilenum = MF_MinifontSave.tilenum = MINIFONT; + MF_Redfont.emptychar.y = tilesiz[BIGALPHANUM].y<<16; + MF_Bluefont.emptychar.y = MF_BluefontRed.emptychar.y = MF_BluefontGame.emptychar.y = tilesiz[STARTALPHANUM].y<<16; + MF_Minifont.emptychar.y = MF_MinifontRed.emptychar.y = MF_MinifontSave.emptychar.y = tilesiz[MINIFONT].y<<16; + if (NAM_WW2GI) + MF_Bluefont.between.x = MF_BluefontRed.between.x = 0; + if (!minitext_lowercase) + { + MF_Minifont.textflags |= TEXT_UPPERCASE; + MF_MinifontRed.textflags |= TEXT_UPPERCASE; + MF_MinifontSave.textflags |= TEXT_UPPERCASE; + } + + // prepare gamefuncs and keys + MEOSN_Gamefuncs[0] = MenuGameFuncNone; + MEOSV_Gamefuncs[0] = -1; + k = 1; + for (i = 0; i < NUMGAMEFUNCTIONS; ++i) + { + Bstrcpy(MenuGameFuncs[i], gamefunctions[i]); + + for (j = 0; j < MAXGAMEFUNCLEN; ++j) + if (MenuGameFuncs[i][j] == '_') + MenuGameFuncs[i][j] = ' '; + + if (gamefunctions[i][0] != '\0') + { + MEOSN_Gamefuncs[k] = MenuGameFuncs[i]; + MEOSV_Gamefuncs[k] = i; + ++k; + } + } + MEOS_Gamefuncs.numOptions = k; + + for (i = 0; i < NUMKEYS; ++i) + MEOSN_Keys[i] = key_names[i]; + MEOSN_Keys[NUMKEYS-1] = MenuKeyNone; + + + // prepare episodes + k = 0; + for (i = 0; i < g_volumeCnt; ++i) + { + if (g_volumeNames[i][0]) + { + if (!(g_volumeFlags[i] & EF_HIDEFROMSP)) + { + MEL_EPISODE[i] = &ME_EPISODE[i]; + ME_EPISODE[i] = ME_EPISODE_TEMPLATE; + ME_EPISODE[i].name = g_volumeNames[i]; + } + + // if (!(EpisodeFlags[i] & EF_HIDEFROMMP)) + { + MEOSN_NetEpisodes[k] = g_volumeNames[i]; + MEOSV_NetEpisodes[k] = i; + + k++; + } + } + + // prepare levels + MEOS_NETOPTIONS_LEVEL[i] = MEOS_NETOPTIONS_LEVEL_TEMPLATE; + for (j = 0; j < MAXLEVELS; ++j) + { + MEOSN_NetLevels[i][j] = g_mapInfo[MAXLEVELS*i+j].name; + if (g_mapInfo[i*MAXLEVELS+j].filename != NULL) + MEOS_NETOPTIONS_LEVEL[i].numOptions = j+1; + } + MEOS_NETOPTIONS_LEVEL[i].optionNames = MEOSN_NetLevels[i]; + } + M_EPISODE.numEntries = g_volumeCnt+2; +#ifndef EDUKE32_SIMPLE_MENU + MEL_EPISODE[g_volumeCnt] = &ME_Space4_Redfont; + MEL_EPISODE[g_volumeCnt+1] = &ME_EPISODE_USERMAP; + MEOSN_NetEpisodes[k] = MenuUserMap; + MEOSV_NetEpisodes[k] = MAXVOLUMES; +#else + M_EPISODE.numEntries = g_volumeCnt; + k--; +#endif + MEOS_NETOPTIONS_EPISODE.numOptions = k + 1; + NetEpisode = MEOSV_NetEpisodes[0]; + MMF_Top_Episode.pos.y = (58 + (3-k)*6)<<16; + if (g_skillCnt == 0) + MEO_EPISODE.linkID = MENU_NULL; + + // prepare skills + k = -1; + for (i = 0; i < g_skillCnt && g_skillNames[i][0]; ++i) + { + MEL_SKILL[i] = &ME_SKILL[i]; + ME_SKILL[i] = ME_SKILL_TEMPLATE; + ME_SKILL[i].name = g_skillNames[i]; + + MEOSN_NetSkills[i] = g_skillNames[i]; + + k = i; + } + ++k; + M_SKILL.numEntries = g_skillCnt; // k; + MEOS_NETOPTIONS_MONSTERS.numOptions = g_skillCnt + 1; // k+1; + MEOSN_NetSkills[g_skillCnt] = MenuSkillNone; + MMF_Top_Skill.pos.y = (58 + (4-g_skillCnt)*6)<<16; + M_SKILL.currentEntry = 1; + Menu_AdjustForCurrentEntryAssignmentBlind(&M_SKILL); + + // prepare multiplayer gametypes + k = -1; + for (i = 0; i < MAXGAMETYPES; ++i) + if (g_gametypeNames[i][0]) + { + MEOSN_NetGametypes[i] = g_gametypeNames[i]; + k = i; + } + ++k; + MEOS_NETOPTIONS_GAMETYPE.numOptions = k; + if (NAM_WW2GI) + ME_NETOPTIONS_MONSTERS.name = "Enemies"; + + // prepare text chat macros + for (i = 0; i < MAXRIDECULE; ++i) + { + MEL_MACROS[i] = &ME_MACROS[i]; + ME_MACROS[i] = ME_MACROS_TEMPLATE; + ME_MACROS[i].entry = &MEO_MACROS[i]; + MEO_MACROS[i] = MEO_MACROS_TEMPLATE; + + MEO_MACROS[i].variable = ud.ridecule[i]; + } + + // prepare input + for (i = 0; i < NUMGAMEFUNCTIONS; ++i) + { + if (MenuGameFuncs[i][0] == '\0') + { + MEL_KEYBOARDSETUPFUNCS[i] = NULL; + continue; + } + + MEL_KEYBOARDSETUPFUNCS[i] = &ME_KEYBOARDSETUPFUNCS[i]; + ME_KEYBOARDSETUPFUNCS[i] = ME_KEYBOARDSETUPFUNCS_TEMPLATE; + ME_KEYBOARDSETUPFUNCS[i].name = MenuGameFuncs[i]; + ME_KEYBOARDSETUPFUNCS[i].entry = &MEO_KEYBOARDSETUPFUNCS[i]; + MEO_KEYBOARDSETUPFUNCS[i] = MEO_KEYBOARDSETUPFUNCS_TEMPLATE; + MEO_KEYBOARDSETUPFUNCS[i].column[0] = &ud.config.KeyboardKeys[i][0]; + MEO_KEYBOARDSETUPFUNCS[i].column[1] = &ud.config.KeyboardKeys[i][1]; + } + M_KEYBOARDKEYS.numEntries = NUMGAMEFUNCTIONS; + for (i = 0; i < MENUMOUSEFUNCTIONS; ++i) + { + MEL_MOUSESETUPBTNS[i] = &ME_MOUSESETUPBTNS[i]; + ME_MOUSESETUPBTNS[i] = ME_MOUSEJOYSETUPBTNS_TEMPLATE; + ME_MOUSESETUPBTNS[i].name = MenuMouseNames[i]; + ME_MOUSESETUPBTNS[i].entry = &MEO_MOUSESETUPBTNS[i]; + MEO_MOUSESETUPBTNS[i] = MEO_MOUSEJOYSETUPBTNS_TEMPLATE; + MEO_MOUSESETUPBTNS[i].data = &ud.config.MouseFunctions[MenuMouseDataIndex[i][0]][MenuMouseDataIndex[i][1]]; + } + for (i = 0; i < 2*joynumbuttons + 8*joynumhats; ++i) + { + if (i < 2*joynumbuttons) + { + if (i & 1) + Bsnprintf(MenuJoystickNames[i], MAXJOYBUTTONSTRINGLENGTH, "Double %s", getjoyname(1, i>>1)); + else + Bstrncpy(MenuJoystickNames[i], getjoyname(1, i>>1), MAXJOYBUTTONSTRINGLENGTH); + } + else + { + Bsnprintf(MenuJoystickNames[i], MAXJOYBUTTONSTRINGLENGTH, (i & 1) ? "Double Hat %d %s" : "Hat %d %s", ((i - 2*joynumbuttons)>>3), MenuJoystickHatDirections[((i - 2*joynumbuttons)>>1) % 4]); + } + + MEL_JOYSTICKBTNS[i] = &ME_JOYSTICKBTNS[i]; + ME_JOYSTICKBTNS[i] = ME_MOUSEJOYSETUPBTNS_TEMPLATE; + ME_JOYSTICKBTNS[i].name = MenuJoystickNames[i]; + ME_JOYSTICKBTNS[i].entry = &MEO_JOYSTICKBTNS[i]; + MEO_JOYSTICKBTNS[i] = MEO_MOUSEJOYSETUPBTNS_TEMPLATE; + MEO_JOYSTICKBTNS[i].data = &ud.config.JoystickFunctions[i>>1][i&1]; + } + M_JOYSTICKBTNS.numEntries = 2*joynumbuttons + 8*joynumhats; + for (i = 0; i < joynumaxes; ++i) + { + ME_JOYSTICKAXES[i] = ME_JOYSTICKAXES_TEMPLATE; + Bstrncpy(MenuJoystickAxes[i], getjoyname(0, i), MAXJOYBUTTONSTRINGLENGTH); + ME_JOYSTICKAXES[i].name = MenuJoystickAxes[i]; + MEL_JOYSTICKAXES[i] = &ME_JOYSTICKAXES[i]; + } + M_JOYSTICKAXES.numEntries = joynumaxes; + + // prepare video setup + for (i = 0; i < validmodecnt; ++i) + { + int32_t *const r = &MEOS_VIDEOSETUP_RESOLUTION.numOptions; + + for (j = 0; j < *r; ++j) + { + if (validmode[i].xdim == resolution[j].xdim && validmode[i].ydim == resolution[j].ydim) + { + resolution[j].flags |= validmode[i].fs ? RES_FS : RES_WIN; + if (validmode[i].bpp > resolution[j].bppmax) + resolution[j].bppmax = validmode[i].bpp; + break; + } + } + + if (j == *r) // no match found + { + resolution[j].xdim = validmode[i].xdim; + resolution[j].ydim = validmode[i].ydim; + resolution[j].bppmax = validmode[i].bpp; + resolution[j].flags = validmode[i].fs ? RES_FS : RES_WIN; + Bsnprintf(resolution[j].name, MAXRESOLUTIONSTRINGLENGTH, "%d x %d", resolution[j].xdim, resolution[j].ydim); + MEOSN_VIDEOSETUP_RESOLUTION[j] = resolution[j].name; + ++*r; + } + } + + // prepare sound setup +#ifndef EDUKE32_STANDALONE + if (WW2GI) + ME_SOUND_DUKETALK.name = "GI talk:"; + else if (NAM) + ME_SOUND_DUKETALK.name = "Grunt talk:"; +#endif + + if (KXDWN) + { + MF_Redfont.between.x = 2<<16; + MF_Redfont.cursorScale = 32768; + MF_Redfont.zoom = 16384; + MF_Bluefont.between.x = MF_BluefontRed.between.x = 0; + MF_Bluefont.zoom = MF_BluefontRed.zoom = MF_BluefontGame.zoom = 16384; + + // hack; should swap out pointers + MF_Minifont = MF_Bluefont; + MF_MinifontRed = MF_BluefontRed; + MF_MinifontSave.zoom = 32768; + + MMF_Top_Main.pos.x = 40<<16; + MMF_Top_Main.pos.y = 130<<16; + MMF_Top_Main.bottomcutoff = 190<<16; + M_OPTIONS.format = &MMF_Top_Main; + + MEF_MainMenu.width = MEF_OptionsMenu.width = -(160<<16); + MEF_MainMenu.marginBottom = 7<<16; + + M_OPTIONS.title = NoTitle; + } + + // prepare shareware + if (VOLUMEONE) + { + // blue out episodes beyond the first + for (i = 1; i < g_volumeCnt; ++i) + { + if (MEL_EPISODE[i]) + { + ME_EPISODE[i].entry = &MEO_EPISODE_SHAREWARE; + ME_EPISODE[i].flags |= MEF_LookDisabled; + } + } + M_EPISODE.numEntries = g_volumeCnt; // remove User Map (and spacer) + MEOS_NETOPTIONS_EPISODE.numOptions = 1; + MenuEntry_DisableOnCondition(&ME_NETOPTIONS_EPISODE, 1); + } + + // prepare pre-Atomic + if (!VOLUMEALL || !PLUTOPAK) + { + // prepare credits + M_CREDITS.title = M_CREDITS2.title = M_CREDITS3.title = s_Credits; + } +} + +static void Menu_Run(Menu_t *cm, vec2_t origin); + + +static void Menu_BlackRectangle(int32_t x, int32_t y, int32_t width, int32_t height, int32_t orientation); + +/* +At present, no true difference is planned between Menu_Pre() and Menu_PreDraw(). +They are separate for purposes of organization. +*/ + +static void Menu_Pre(MenuID_t cm) +{ + int32_t i; + DukePlayer_t *ps = g_player[myconnectindex].ps; + + switch (cm) + { + case MENU_MAIN_INGAME: + MenuEntry_DisableOnCondition(&ME_MAIN_SAVEGAME, ud.recstat == 2); + MenuEntry_DisableOnCondition(&ME_MAIN_QUITTOTITLE, g_netServer || numplayers > 1); + fallthrough__; + case MENU_MAIN: + if ((g_netServer || ud.multimode > 1) && ud.recstat != 2) + { + ME_MAIN_NEWGAME.entry = &MEO_MAIN_NEWGAME_NETWORK; + ME_MAIN_NEWGAME_INGAME.entry = &MEO_MAIN_NEWGAME_NETWORK; + } + else + { + ME_MAIN_NEWGAME.entry = &MEO_MAIN_NEWGAME; + ME_MAIN_NEWGAME_INGAME.entry = &MEO_MAIN_NEWGAME_INGAME; + } + break; + + case MENU_GAMESETUP: + MEO_GAMESETUP_DEMOREC.options = (ps->gm&MODE_GAME) ? &MEOS_DemoRec : &MEOS_OffOn; + MenuEntry_DisableOnCondition(&ME_GAMESETUP_DEMOREC, (ps->gm&MODE_GAME) && ud.m_recstat != 1); + break; + +#ifdef USE_OPENGL + case MENU_DISPLAYSETUP: + if (getrendermode() == REND_CLASSIC) + MenuMenu_ChangeEntryList(M_DISPLAYSETUP, MEL_DISPLAYSETUP); +#ifdef POLYMER + else if (getrendermode() == REND_POLYMER) + MenuMenu_ChangeEntryList(M_DISPLAYSETUP, MEL_DISPLAYSETUP_GL_POLYMER); +#endif + else + MenuMenu_ChangeEntryList(M_DISPLAYSETUP, MEL_DISPLAYSETUP_GL); + + vpsize = ud.screen_size + 4*(ud.screen_size>=8 && ud.statusbarmode==0) + 4*(ud.screen_size>=4 && ud.althud==0); + + if (getrendermode() != REND_CLASSIC) + { + for (i = (int32_t) ARRAY_SIZE(MEOSV_DISPLAYSETUP_ANISOTROPY) - 1; i >= 0; --i) + { + if (MEOSV_DISPLAYSETUP_ANISOTROPY[i] <= glinfo.maxanisotropy) + { + MEOS_DISPLAYSETUP_ANISOTROPY.numOptions = i + 1; + break; + } + } + } + break; + + case MENU_POLYMER: + case MENU_POLYMOST: + MenuEntry_DisableOnCondition(&ME_RENDERERSETUP_TEXQUALITY, !usehightile); + MenuEntry_DisableOnCondition(&ME_RENDERERSETUP_PRECACHE, !usehightile); +# ifndef EDUKE32_GLES + MenuEntry_DisableOnCondition(&ME_RENDERERSETUP_TEXCACHE, !(glusetexcompr && usehightile)); +# endif +# ifdef USE_GLEXT + MenuEntry_DisableOnCondition(&ME_RENDERERSETUP_DETAILTEX, !usehightile); + MenuEntry_DisableOnCondition(&ME_RENDERERSETUP_GLOWTEX, !usehightile); +# endif + break; +#endif + + case MENU_VIDEOSETUP: + { + const int32_t nr = newresolution; + + MenuEntry_DisableOnCondition(&ME_VIDEOSETUP_APPLY, + (xdim == resolution[nr].xdim && ydim == resolution[nr].ydim && + getrendermode() == newrendermode && fullscreen == newfullscreen + && vsync == newvsync + ) + || (newfullscreen ? !(resolution[nr].flags & RES_FS) : !(resolution[nr].flags & RES_WIN)) + || (newrendermode != REND_CLASSIC && resolution[nr].bppmax <= 8)); + break; + } + + case MENU_SOUND: + case MENU_SOUND_INGAME: + case MENU_ADVSOUND: + MenuEntry_DisableOnCondition(&ME_SOUND_VOLUME_MASTER, !ud.config.SoundToggle && !ud.config.MusicToggle); + MenuEntry_DisableOnCondition(&ME_SOUND_VOLUME_EFFECTS, !ud.config.SoundToggle); + MenuEntry_DisableOnCondition(&ME_SOUND_VOLUME_MUSIC, !ud.config.MusicToggle); + MenuEntry_DisableOnCondition(&ME_SOUND_DUKETALK, !ud.config.SoundToggle); + MenuEntry_DisableOnCondition(&ME_SOUND_SAMPLINGRATE, !ud.config.SoundToggle && !ud.config.MusicToggle); +#ifndef EDUKE32_SIMPLE_MENU + MenuEntry_DisableOnCondition(&ME_SOUND_NUMVOICES, !ud.config.SoundToggle); +#endif + MenuEntry_DisableOnCondition(&ME_SOUND_FLUIDSYNTH_BACKEND, !ud.config.SoundToggle && !ud.config.MusicToggle); + MenuEntry_DisableOnCondition(&ME_SOUND_RESTART, soundrate == ud.config.MixRate && + soundvoices == ud.config.NumVoices && + musicbackend == ud.config.FluidsynthBackend); + break; + +#ifndef EDUKE32_SIMPLE_MENU + case MENU_MOUSESETUP: + MenuEntry_DisableOnCondition(&ME_MOUSESETUP_MOUSEAIMING, ud.mouseaiming); + break; +#endif + case MENU_NETOPTIONS: + if (MEOSV_NetEpisodes[MEO_NETOPTIONS_EPISODE.currentOption] == MAXVOLUMES) + MEL_NETOPTIONS[2] = &ME_NETOPTIONS_USERMAP; + else + { + MEL_NETOPTIONS[2] = &ME_NETOPTIONS_LEVEL; + MEO_NETOPTIONS_LEVEL.options = &MEOS_NETOPTIONS_LEVEL[MEOSV_NetEpisodes[MEO_NETOPTIONS_EPISODE.currentOption]]; + } + if (!(g_gametypeFlags[ud.m_coop] & GAMETYPE_MARKEROPTION)) + { + ME_NETOPTIONS_MARKERS.type = Dummy; + ME_NETOPTIONS_MARKERS.flags |= MEF_Disabled; + } + else + { + ME_NETOPTIONS_MARKERS.type = Option; + ME_NETOPTIONS_MARKERS.flags &= ~MEF_Disabled; + } + MEL_NETOPTIONS[5] = (g_gametypeFlags[ud.m_coop] & (GAMETYPE_PLAYERSFRIENDLY|GAMETYPE_TDM)) ? &ME_NETOPTIONS_FRFIRE : &ME_NETOPTIONS_MAPEXITS; + break; + + case MENU_OPTIONS: + MenuEntry_DisableOnCondition(&ME_OPTIONS_PLAYERSETUP, ud.recstat == 1); + MenuEntry_DisableOnCondition(&ME_OPTIONS_JOYSTICKSETUP, CONTROL_JoyPresent == 0); + break; + + case MENU_COLCORR: + case MENU_COLCORR_INGAME: + MenuEntry_DisableOnCondition(&ME_COLCORR_CONTRAST, !gammabrightness); + MenuEntry_DisableOnCondition(&ME_COLCORR_BRIGHTNESS, !gammabrightness); + break; + + case MENU_CHEATS: + case MENU_CHEATENTRY: + case MENU_CHEAT_WARP: + case MENU_CHEAT_SKILL: + { + const int32_t menucheatsdisabled = numplayers != 1 || !(g_player[myconnectindex].ps->gm & MODE_GAME); + + for (i = 0; i < NUMCHEATFUNCS; i++) + { + uint32_t cheatmask = cl_cheatmask & (1<<i); + + // KEEPINSYNC: NAM_WW2GI_CHEATS + if (NAM_WW2GI) + { + switch (i) + { + case CHEATFUNC_CASHMAN: + case CHEATFUNC_GIVEALLITEMS: + case CHEATFUNC_QUOTEBETA: + case CHEATFUNC_MONSTERS: + case CHEATFUNC_QUOTEALLEN: + case CHEATFUNC_GIVEKEYS: + cheatmask = 0; + break; + } + } + if (WW2GI) + { + switch (i) + { + case CHEATFUNC_HYPER: + cheatmask = 0; + break; + } + } + + // only show cheats that have been typed in before + MEL_CHEATS[i+1] = cheatmask ? &ME_CheatCodes[i] : NULL; + + // disable outside of a single-player game + MenuEntry_DisableOnCondition(&ME_CheatCodes[i], menucheatsdisabled); + } + + // refresh display names of quote cheats + if (!DUKEBETA) + { + ME_CheatCodes[CHEATFUNC_QUOTEBETA].name = apStrings[QUOTE_CHEAT_BETA]; + ME_CheatCodes[CHEATFUNC_QUOTETODD].name = NAM ? g_NAMMattCheatQuote : apStrings[QUOTE_CHEAT_TODD]; + ME_CheatCodes[CHEATFUNC_QUOTEALLEN].name = apStrings[QUOTE_CHEAT_ALLEN]; + } + + MenuEntry_DisableOnCondition(&ME_ENTERCHEAT, (cl_cheatmask == UINT32_MAX)); + + break; + } + + default: + break; + } +} + + +static void Menu_PreDrawBackground(MenuID_t cm, const vec2_t origin) +{ + switch (cm) + { + case MENU_CREDITS: + case MENU_CREDITS2: + case MENU_CREDITS3: + if (!VOLUMEALL || !PLUTOPAK) + Menu_DrawBackground(origin); + else + rotatesprite_fs(origin.x + (MENU_MARGIN_CENTER<<16), origin.y + (100<<16), 65536L,0,2504+cm-MENU_CREDITS,0,0,10+64); + break; + + case MENU_LOAD: + case MENU_SAVE: + case MENU_CREDITS4: + case MENU_CREDITS5: + Menu_DrawBackground(origin); + break; + + case MENU_STORY: + rotatesprite_fs(origin.x + (MENU_MARGIN_CENTER<<16), origin.y + (100<<16), 65536L,0,TEXTSTORY,0,0,10+64); + break; + + case MENU_F1HELP: + rotatesprite_fs(origin.x + (MENU_MARGIN_CENTER<<16), origin.y + (100<<16), 65536L,0,F1HELP,0,0,10+64); + break; + } +} + + +static void Menu_PreDraw(MenuID_t cm, MenuEntry_t *entry, const vec2_t origin) +{ + int32_t i, j, l = 0, m; + + switch (cm) + { + case MENU_MAIN_INGAME: + l += 4; + fallthrough__; + case MENU_MAIN: + if ((G_GetLogoFlags() & LOGO_NOGAMETITLE) == 0) + { + rotatesprite_fs(origin.x + (MENU_MARGIN_CENTER<<16), origin.y + ((28+l)<<16), 65536L,0,INGAMEDUKETHREEDEE,0,0,10); + if (PLUTOPAK) // JBF 20030804 + rotatesprite_fs(origin.x + ((MENU_MARGIN_CENTER+100)<<16), origin.y + (36<<16), 65536L,0,PLUTOPAKSPRITE+2,(sintable[(totalclock<<4)&2047]>>11),0,2+8); + } + break; + + case MENU_PLAYER: + rotatesprite_fs(origin.x + (260<<16), origin.y + ((24+(tilesiz[APLAYER].y>>1))<<16), 49152L,0,1441-((((4-(totalclock>>4)))&3)*5),0,entry == &ME_PLAYER_TEAM ? G_GetTeamPalette(ud.team) : ud.color,10); + break; + + case MENU_MACROS: + mgametextcenter(origin.x, origin.y + (144<<16), "Activate in-game with Shift-F#"); + break; + + case MENU_COLCORR: + case MENU_COLCORR_INGAME: + // center panel + rotatesprite_fs(origin.x + (120<<16), origin.y + (32<<16), 16384, 0, 3290, 0, 0, 2|8|16); + rotatesprite_fs(origin.x + (160<<16) - (tilesiz[BOTTOMSTATUSBAR].x<<13), origin.y + (82<<16) - (tilesiz[BOTTOMSTATUSBAR].y<<14), 16384, 0, BOTTOMSTATUSBAR, 0, 0, 2|8|16); + + // left panel + rotatesprite_fs(origin.x + (40<<16), origin.y + (32<<16), 16384, 0, BONUSSCREEN, 0, 0, 2|8|16); + + // right panel + rotatesprite_fs(origin.x + (200<<16), origin.y + (32<<16), 16384, 0, LOADSCREEN, 0, 0, 2|8|16); + break; + + case MENU_NETSETUP: + case MENU_NETHOST: + mminitext(origin.x + (90<<16), origin.y + (90<<16), "Game Type", 2); + mminitext(origin.x + (90<<16), origin.y + ((90+8)<<16), "Episode", 2); + mminitext(origin.x + (90<<16), origin.y + ((90+8+8)<<16), "Level", 2); + mminitext(origin.x + (90<<16), origin.y + ((90+8+8+8)<<16), ME_NETOPTIONS_MONSTERS.name, 2); + if (ud.m_coop == 0) + mminitext(origin.x + (90<<16), origin.y + ((90+8+8+8+8)<<16), "Markers", 2); + else if (ud.m_coop == 1) + mminitext(origin.x + (90<<16), origin.y + ((90+8+8+8+8)<<16), "Friendly Fire", 2); + mminitext(origin.x + (90<<16), origin.y + ((90+8+8+8+8+8)<<16), "User Map", 2); + + mminitext(origin.x + ((90+60)<<16), origin.y + (90<<16), g_gametypeNames[ud.m_coop], 0); + + mminitext(origin.x + ((90+60)<<16), origin.y + ((90+8)<<16), g_volumeNames[ud.m_volume_number], 0); + mminitext(origin.x + ((90+60)<<16), origin.y + ((90+8+8)<<16), g_mapInfo[MAXLEVELS*ud.m_volume_number+ud.m_level_number].name, 0); + if (ud.m_monsters_off == 0 || ud.m_player_skill > 0) + mminitext(origin.x + ((90+60)<<16), origin.y + ((90+8+8+8)<<16), g_skillNames[ud.m_player_skill], 0); + else mminitext(origin.x + ((90+60)<<16), origin.y + ((90+8+8+8)<<16), "None", 0); + if (ud.m_coop == 0) + { + if (ud.m_marker) mminitext(origin.x + ((90+60)<<16), origin.y + ((90+8+8+8+8)<<16), "On", 0); + else mminitext(origin.x + ((90+60)<<16), origin.y + ((90+8+8+8+8)<<16), "Off", 0); + } + else if (ud.m_coop == 1) + { + if (ud.m_ffire) mminitext(origin.x + ((90+60)<<16), origin.y + ((90+8+8+8+8)<<16), "On", 0); + else mminitext(origin.x + ((90+60)<<16), origin.y + ((90+8+8+8+8)<<16), "Off", 0); + } + break; + + case MENU_MOUSEADVANCED: + { + size_t i; + for (i = 0; i < ARRAY_SIZE(MEL_INTERNAL_MOUSEADVANCED_DAXES); i++) + if (entry == MEL_INTERNAL_MOUSEADVANCED_DAXES[i]) + { + mgametextcenter(origin.x, origin.y + (162<<16), "Digital axes are not for mouse look\n" + "or for aiming up and down"); + } + } + break; + + case MENU_RESETPLAYER: + fade_screen_black(1); + Bsprintf(tempbuf, "Load last game:\n\"%s\"" +#ifndef EDUKE32_ANDROID_MENU + "\n(Y/N)" +#endif + , g_quickload->name); + mgametextcenter(origin.x, origin.y + (90<<16), tempbuf); + break; + + case MENU_LOAD: + { +#if 0 + for (i = 0; i <= 108; i += 12) + rotatesprite_fs(origin.x + ((160+64+91-64)<<16), origin.y + ((i+56)<<16), 65536L,0,TEXTBOX,24,0,10); +#endif + Menu_BlackRectangle(origin.x + (198<<16), origin.y + (47<<16), 102<<16, 100<<16, 1|32); + + rotatesprite_fs(origin.x + (22<<16), origin.y + (97<<16), 65536L,0,WINDOWBORDER2,24,0,10); + rotatesprite_fs(origin.x + (180<<16), origin.y + (97<<16), 65536L,1024,WINDOWBORDER2,24,0,10); + rotatesprite_fs(origin.x + (99<<16), origin.y + (50<<16), 65536L,512,WINDOWBORDER1,24,0,10); + rotatesprite_fs(origin.x + (103<<16), origin.y + (144<<16), 65536L,1024+512,WINDOWBORDER1,24,0,10); + + if (M_LOAD.currentEntry >= (int32_t)g_nummenusaves) + { + mmenutext(origin.x + (72<<16), origin.y + (100<<16), "Empty"); + break; + } + + menusave_t & msv = g_menusaves[M_LOAD.currentEntry]; + + if (msv.brief.isValid()) + { + rotatesprite_fs(origin.x + (101<<16), origin.y + (97<<16), 65536>>1,512,TILE_LOADSHOT,-32,0,4+10+64); + + if (msv.isOldVer) + { + mmenutext(origin.x + (53<<16), origin.y + (70<<16), "Previous"); + mmenutext(origin.x + (58<<16), origin.y + (90<<16), "Version"); + +#ifndef EDUKE32_SIMPLE_MENU + Bsprintf(tempbuf,"Saved: %d.%d.%d %d-bit", savehead.majorver, savehead.minorver, + savehead.bytever, 8*savehead.ptrsize); + mgametext(origin.x + (31<<16), origin.y + (104<<16), tempbuf); + Bsprintf(tempbuf,"Our: %d.%d.%d %d-bit", SV_MAJOR_VER, SV_MINOR_VER, BYTEVERSION, + (int32_t)(8*sizeof(intptr_t))); + mgametext(origin.x + ((31+16)<<16), origin.y + (114<<16), tempbuf); +#endif + } + + if (savehead.numplayers > 1) + { + Bsprintf(tempbuf, "Players: %-2d ", savehead.numplayers); + mgametextcenter(origin.x, origin.y + (156<<16), tempbuf); + } + + { + const char *name = g_mapInfo[(savehead.volnum*MAXLEVELS) + savehead.levnum].name; + Bsprintf(tempbuf, "%s / %s", name ? name : "^10unnamed^0", g_skillNames[savehead.skill-1]); + } + + mgametextcenter(origin.x, origin.y + (168<<16), tempbuf); + if (savehead.volnum == 0 && savehead.levnum == 7) + mgametextcenter(origin.x, origin.y + (180<<16), savehead.boardfn); + } + break; + } + + case MENU_SAVE: + { +#if 0 + for (i = 0; i <= 108; i += 12) + rotatesprite_fs(origin.x + ((160+64+91-64)<<16), origin.y + ((i+56)<<16), 65536L,0,TEXTBOX,24,0,10); +#endif + Menu_BlackRectangle(origin.x + (198<<16), origin.y + (47<<16), 102<<16, 100<<16, 1|32); + + rotatesprite_fs(origin.x + (22<<16), origin.y + (97<<16), 65536L,0,WINDOWBORDER2,24,0,10); + rotatesprite_fs(origin.x + (180<<16), origin.y + (97<<16), 65536L,1024,WINDOWBORDER2,24,0,10); + rotatesprite_fs(origin.x + (99<<16), origin.y + (50<<16), 65536L,512,WINDOWBORDER1,24,0,10); + rotatesprite_fs(origin.x + (103<<16), origin.y + (144<<16), 65536L,1024+512,WINDOWBORDER1,24,0,10); + + j = 0; + for (size_t k = 0; k < g_nummenusaves+1; ++k) + if (((MenuString_t*)M_SAVE.entrylist[k]->entry)->editfield) + j |= 1; + + if (j) + rotatesprite_fs(origin.x + (101<<16), origin.y + (97<<16), 65536L>>1,512,TILE_SAVESHOT,-32,0,4+10+64); + else if (0 < M_SAVE.currentEntry && M_SAVE.currentEntry <= (int32_t)g_nummenusaves) + { + if (g_menusaves[M_SAVE.currentEntry-1].brief.isValid()) + { + rotatesprite_fs(origin.x + (101<<16), origin.y + (97<<16), 65536L>>1,512,TILE_LOADSHOT,-32,0,4+10+64); + + if (g_menusaves[M_SAVE.currentEntry-1].isOldVer) + { + mmenutext(origin.x + (53<<16), origin.y + (70<<16), "Previous"); + mmenutext(origin.x + (58<<16), origin.y + (90<<16), "Version"); + +#ifndef EDUKE32_SIMPLE_MENU + Bsprintf(tempbuf,"Saved: %d.%d.%d %d-bit", savehead.majorver, savehead.minorver, + savehead.bytever, 8*savehead.ptrsize); + mgametext(origin.x + (31<<16), origin.y + (104<<16), tempbuf); + Bsprintf(tempbuf,"Our: %d.%d.%d %d-bit", SV_MAJOR_VER, SV_MINOR_VER, BYTEVERSION, + (int32_t)(8*sizeof(intptr_t))); + mgametext(origin.x + ((31+16)<<16), origin.y + (114<<16), tempbuf); +#endif + } + } + } + else + mmenutext(origin.x + (82<<16), origin.y + (100<<16), "New"); + + if (ud.multimode > 1) + { + Bsprintf(tempbuf, "Players: %-2d ", ud.multimode); + mgametextcenter(origin.x, origin.y + (156<<16), tempbuf); + } + + Bsprintf(tempbuf,"%s / %s",g_mapInfo[(ud.volume_number*MAXLEVELS) + ud.level_number].name, g_skillNames[ud.player_skill-1]); + mgametextcenter(origin.x, origin.y + (168<<16), tempbuf); + if (ud.volume_number == 0 && ud.level_number == 7) + mgametextcenter(origin.x, origin.y + (180<<16), currentboardfilename); + break; + } + +#ifdef EDUKE32_ANDROID_MENU + case MENU_SKILL: + { + static const char *s[] = { "EASY - Few enemies, and lots of stuff.", "MEDIUM - Normal difficulty.", "HARD - For experienced players.", "EXPERTS - Lots of enemies, plus they respawn!" }; + if ((size_t)M_SKILL.currentEntry < ARRAY_SIZE(s)) + mgametextcenter(origin.x, origin.y + (168<<16), s[M_SKILL.currentEntry]); + } + break; +#endif + + case MENU_LOADVERIFY: + { + fade_screen_black(1); + menusave_t & msv = g_menusaves[M_LOAD.currentEntry]; + if (msv.isOldVer) + { + Bsprintf(tempbuf, "Start new game:\n%s / %s" +#ifndef EDUKE32_ANDROID_MENU + "\n(Y/N)" +#endif + , g_mapInfo[(ud.volume_number*MAXLEVELS) + ud.level_number].name, g_skillNames[ud.player_skill-1]); + mgametextcenter(origin.x, origin.y + (90<<16), tempbuf); + } + else + { + Bsprintf(tempbuf, "Load game:\n\"%s\"" +#ifndef EDUKE32_ANDROID_MENU + "\n(Y/N)" +#endif + , msv.brief.name); + mgametextcenter(origin.x, origin.y + (90<<16), tempbuf); + } + break; + } + + case MENU_SAVEVERIFY: + fade_screen_black(1); + mgametextcenter(origin.x, origin.y + (90<<16), "Overwrite previous saved game?" +#ifndef EDUKE32_ANDROID_MENU + "\n(Y/N)" +#endif + ); + break; + + case MENU_NEWVERIFY: + fade_screen_black(1); + mgametextcenter(origin.x, origin.y + (90<<16), "Abort this game?" +#ifndef EDUKE32_ANDROID_MENU + "\n(Y/N)" +#endif + ); + break; + + case MENU_QUIT: + case MENU_QUIT_INGAME: + fade_screen_black(1); + mgametextcenter(origin.x, origin.y + (90<<16), "Are you sure you want to quit?" +#ifndef EDUKE32_ANDROID_MENU + "\n(Y/N)" +#endif + ); + break; + + case MENU_QUITTOTITLE: + fade_screen_black(1); + mgametextcenter(origin.x, origin.y + (90<<16), "End game and return to title screen?" +#ifndef EDUKE32_ANDROID_MENU + "\n(Y/N)" +#endif + ); + break; + + case MENU_NETWAITMASTER: + G_DrawFrags(); + mgametextcenter(origin.x, origin.y + (50<<16), "Waiting for master\n" + "to select level"); + break; + + case MENU_NETWAITVOTES: + G_DrawFrags(); + mgametextcenter(origin.x, origin.y + (90<<16), "Waiting for votes"); + break; + +#ifndef EDUKE32_STANDALONE + case MENU_BUYDUKE: + mgametextcenter(origin.x, origin.y + (33<<16), "You are playing the shareware\n" + "version of Duke Nukem 3D. While\n" + "this version is really cool, you\n" + "are missing over 75% of the total\n" + "game, along with other great extras\n" + "which you'll get when you order\n" + "the complete version and get\n" + "the final three episodes."); + + mgametextcenter(origin.x, origin.y + ((148+16)<<16), "Press any key or button..."); + break; +#endif + case MENU_CREDITS: + case MENU_CREDITS2: + case MENU_CREDITS3: +#ifndef EDUKE32_STANDALONE + if (!VOLUMEALL || !PLUTOPAK) + { + switch (cm) + { + case MENU_CREDITS: + m = origin.x + (20<<16); + l = origin.y + (33<<16); + + shadowminitext(m, l, "Original Concept", 12); l += 7<<16; + shadowminitext(m, l, "Todd Replogle and Allen H. Blum III", 12); l += 7<<16; + l += 3<<16; + shadowminitext(m, l, "Produced & Directed By", 12); l += 7<<16; + shadowminitext(m, l, "Greg Malone", 12); l += 7<<16; + l += 3<<16; + shadowminitext(m, l, "Executive Producer", 12); l += 7<<16; + shadowminitext(m, l, "George Broussard", 12); l += 7<<16; + l += 3<<16; + shadowminitext(m, l, "BUILD Engine", 12); l += 7<<16; + shadowminitext(m, l, "Ken Silverman", 12); l += 7<<16; + l += 3<<16; + shadowminitext(m, l, "Game Programming", 12); l += 7<<16; + shadowminitext(m, l, "Todd Replogle", 12); l += 7<<16; + l += 3<<16; + shadowminitext(m, l, "3D Engine/Tools/Net", 12); l += 7<<16; + shadowminitext(m, l, "Ken Silverman", 12); l += 7<<16; + l += 3<<16; + shadowminitext(m, l, "Network Layer/Setup Program", 12); l += 7<<16; + shadowminitext(m, l, "Mark Dochtermann", 12); l += 7<<16; + l += 3<<16; + shadowminitext(m, l, "Map Design", 12); l += 7<<16; + shadowminitext(m, l, "Allen H. Blum III", 12); l += 7<<16; + shadowminitext(m, l, "Richard Gray", 12); l += 7<<16; + + m = origin.x + (180<<16); + l = origin.y + (33<<16); + + shadowminitext(m, l, "3D Modeling", 12); l += 7<<16; + shadowminitext(m, l, "Chuck Jones", 12); l += 7<<16; + shadowminitext(m, l, "Sapphire Corporation", 12); l += 7<<16; + l += 3<<16; + shadowminitext(m, l, "Artwork", 12); l += 7<<16; + shadowminitext(m, l, "Dirk Jones, Stephen Hornback", 12); l += 7<<16; + shadowminitext(m, l, "James Storey, David Demaret", 12); l += 7<<16; + shadowminitext(m, l, "Douglas R. Wood", 12); l += 7<<16; + l += 3<<16; + shadowminitext(m, l, "Sound Engine", 12); l += 7<<16; + shadowminitext(m, l, "Jim Dose", 12); l += 7<<16; + l += 3<<16; + shadowminitext(m, l, "Sound & Music Development", 12); l += 7<<16; + shadowminitext(m, l, "Robert Prince", 12); l += 7<<16; + shadowminitext(m, l, "Lee Jackson", 12); l += 7<<16; + l += 3<<16; + shadowminitext(m, l, "Voice Talent", 12); l += 7<<16; + shadowminitext(m, l, "Lani Minella - Voice Producer", 12); l += 7<<16; + shadowminitext(m, l, "Jon St. John as \"Duke Nukem\"", 12); l += 7<<16; + l += 3<<16; + shadowminitext(m, l, "Graphic Design", 12); l += 7<<16; + shadowminitext(m, l, "Packaging, Manual, Ads", 12); l += 7<<16; + shadowminitext(m, l, "Robert M. Atkins", 12); l += 7<<16; + shadowminitext(m, l, "Michael Hadwin", 12); l += 7<<16; + break; + + case MENU_CREDITS2: + m = origin.x + (20<<16); + l = origin.y + (33<<16); + + shadowminitext(m, l, "Special Thanks To", 12); l += 7<<16; + shadowminitext(m, l, "Steven Blackburn, Tom Hall", 12); l += 7<<16; + shadowminitext(m, l, "Scott Miller, Joe Siegler", 12); l += 7<<16; + shadowminitext(m, l, "Terry Nagy, Colleen Compton", 12); l += 7<<16; + shadowminitext(m, l, "HASH, Inc., FormGen, Inc.", 12); l += 7<<16; + l += 3<<16; + shadowminitext(m, l, "The 3D Realms Beta Testers", 12); l += 7<<16; + l += 3<<16; + shadowminitext(m, l, "Nathan Anderson, Wayne Benner", 12); l += 7<<16; + shadowminitext(m, l, "Glenn Brensinger, Rob Brown", 12); l += 7<<16; + shadowminitext(m, l, "Erik Harris, Ken Heckbert", 12); l += 7<<16; + shadowminitext(m, l, "Terry Herrin, Greg Hively", 12); l += 7<<16; + shadowminitext(m, l, "Hank Leukart, Eric Baker", 12); l += 7<<16; + shadowminitext(m, l, "Jeff Rausch, Kelly Rogers", 12); l += 7<<16; + shadowminitext(m, l, "Mike Duncan, Doug Howell", 12); l += 7<<16; + shadowminitext(m, l, "Bill Blair", 12); l += 7<<16; + + m = origin.x + (160<<16); + l = origin.y + (33<<16); + + shadowminitext(m, l, "Company Product Support", 12); l += 7<<16; + l += 3<<16; + shadowminitext(m, l, "The following companies were cool", 12); l += 7<<16; + shadowminitext(m, l, "enough to give us lots of stuff", 12); l += 7<<16; + shadowminitext(m, l, "during the making of Duke Nukem 3D.", 12); l += 7<<16; + l += 3<<16; + shadowminitext(m, l, "Altec Lansing Multimedia", 12); l += 7<<16; + shadowminitext(m, l, "for tons of speakers and the", 12); l += 7<<16; + shadowminitext(m, l, "THX-licensed sound system.", 12); l += 7<<16; + shadowminitext(m, l, "For info call 1-800-548-0620", 12); l += 7<<16; + l += 3<<16; + shadowminitext(m, l, "Creative Labs, Inc.", 12); l += 7<<16; + l += 3<<16; + shadowminitext(m, l, "Thanks for the hardware, guys.", 12); l += 7<<16; + break; + + case MENU_CREDITS3: + mgametextcenter(origin.x, origin.y + (50<<16), "Duke Nukem 3D is a trademark of\n" + "3D Realms Entertainment" + "\n" + "Duke Nukem 3D\n" + "(C) 1996 3D Realms Entertainment"); + +#if !defined(EDUKE32_ANDROID_MENU) && !defined(EDUKE32_STANDALONE) + if (VOLUMEONE) + { + mgametextcenter(origin.x, origin.y + (106<<16), "Please read LICENSE.DOC for shareware\n" + "distribution grants and restrictions."); + } +#endif + mgametextcenter(origin.x, origin.y + ((VOLUMEONE?134:115)<<16), "Made in Dallas, Texas USA"); + break; + } + } + break; +#endif + case MENU_CREDITS4: // JBF 20031220 + l = 7; + + mgametextcenter(origin.x, origin.y + ((50-l)<<16), "Developers"); + creditsminitext(origin.x + (160<<16), origin.y + ((50+10-l)<<16), "Richard \"TerminX\" Gobeille", 8); + creditsminitext(origin.x + (160<<16), origin.y + ((50+7+10-l)<<16), "Evan \"Hendricks266\" Ramos", 8); + + mgametextcenter(origin.x, origin.y + ((80-l)<<16), "Retired developers"); + creditsminitext(origin.x + (160<<16), origin.y + ((80+10-l)<<16), "Pierre-Loup \"Plagman\" Griffais", 8); + creditsminitext(origin.x + (160<<16), origin.y + ((80+7+10-l)<<16), "Philipp \"Helixhorned\" Kutin", 8); + + mgametextcenter(origin.x, origin.y + ((130+7-l)<<16), "Special thanks to"); + creditsminitext(origin.x + (160<<16), origin.y + ((130+7+10-l)<<16), "Jonathon \"JonoF\" Fowler", 8); + + mgametextcenter(origin.x, origin.y + ((150+7-l)<<16), "Uses BUILD Engine technology by"); + creditsminitext(origin.x + (160<<16), origin.y + ((150+7+10-l)<<16), "Ken \"Awesoken\" Silverman", 8); + + + break; + + case MENU_CREDITS5: + l = 7; + + mgametextcenter(origin.x, origin.y + ((38-l)<<16), "License and Other Contributors"); + { + const char *header[] = + { + "This program is distributed under the terms of the", + "GNU General Public License version 2 as published by the", + "Free Software Foundation. See gpl-2.0.txt for details.", + " ", + "The EDuke32 team thanks the following people for their contributions:", + " ", + }; + const char *body[] = + { + "Bioman", // GTK work, APT repository and package upkeep + "Brandon Bergren", // "Bdragon" - tiles.cfg + "Charlie Honig", // "CONAN" - showview command + "Dan Gaskill", // "DeeperThought" - testing + "David Koenig", // "Bargle" - Merged a couple of things from duke3d_w32 + "Ed Coolidge", // Mapster32 improvements + "Emile Belanger", // original Android work + "Hunter_rus", // tons of stuff + "James Bentler", // Mapster32 improvements + "Jasper Foreman", // netcode contributions + "Javier Martinez", // "Malone3D" - EDuke 2.1.1 components + "Jeff Hart", // website graphics + "Jonathan Strander", // "Mblackwell" - testing and feature speccing + "Jose del Castillo", // "Renegado" - EDuke 2.1.1 components + "Lachlan McDonald", // official EDuke32 icon + "LSDNinja", // OS X help and testing + "Marcus Herbert", // "rhoenie" - OS X compatibility work + "Matthew Palmer", // "Usurper" - testing and eduke32.com domain + "Matt Saettler", // original DOS EDuke/WW2GI enhancements + "Ozkan Sezer", // SDL/GTK version checking improvements + "Peter Green", // "Plugwash" - dynamic remapping, custom gametypes + "Peter Veenstra", // "Qbix" - port to 64-bit + "Robin Green", // CON array support + "Ryan Gordon", // "icculus" - icculus.org Duke3D port sound code + "Stephen Anthony", // early 64-bit porting work + "tueidj", // Wii port + " ", + }; + const char *footer[] = + { + " ", + "BUILD engine technology available under license. See buildlic.txt.", + }; + + const int32_t header_numlines = sizeof(header)/sizeof(char *); + const int32_t body_numlines = sizeof(body)/sizeof(char *); + const int32_t footer_numlines = sizeof(footer)/sizeof(char *); + + i = 0; + for (m=0; m<header_numlines; m++) + creditsminitext(origin.x + (160<<16), origin.y + ((17+10+10+8+4+(m*7)-l)<<16), header[m], 8); + i += m; +#define CCOLUMNS 3 +#define CCOLXBUF 20 + for (m=0; m<body_numlines; m++) + creditsminitext(origin.x + ((CCOLXBUF+((320-CCOLXBUF*2)/(CCOLUMNS*2)) +((320-CCOLXBUF*2)/CCOLUMNS)*(m/(body_numlines/CCOLUMNS)))<<16), origin.y + ((17+10+10+8+4+((m%(body_numlines/CCOLUMNS))*7)+(i*7)-l)<<16), body[m], 8); + i += m/CCOLUMNS; + for (m=0; m<footer_numlines; m++) + creditsminitext(origin.x + (160<<16), origin.y + ((17+10+10+8+4+(m*7)+(i*7)-l)<<16), footer[m], 8); + + creditsminitext(origin.x + (160<<16), origin.y + ((138+10+10+10+10+4-l)<<16), "Visit www.eduke32.com for news and updates", 8); + } + + break; + + default: + break; + } +} + + + +static void Menu_PreInput(MenuEntry_t *entry) +{ + switch (g_currentMenu) + { + + case MENU_KEYBOARDKEYS: + if (KB_KeyPressed(sc_Delete)) + { + MenuCustom2Col_t *column = (MenuCustom2Col_t*)entry->entry; + char key[2]; + key[0] = ud.config.KeyboardKeys[M_KEYBOARDKEYS.currentEntry][0]; + key[1] = ud.config.KeyboardKeys[M_KEYBOARDKEYS.currentEntry][1]; + *column->column[M_KEYBOARDKEYS.currentColumn] = 0xff; + CONFIG_MapKey(M_KEYBOARDKEYS.currentEntry, ud.config.KeyboardKeys[M_KEYBOARDKEYS.currentEntry][0], key[0], ud.config.KeyboardKeys[M_KEYBOARDKEYS.currentEntry][1], key[1]); + S_PlaySound(KICK_HIT); + KB_ClearKeyDown(sc_Delete); + } + break; + + default: + break; + } +} + +static void Menu_PreOptionListDraw(MenuEntry_t *entry, const vec2_t origin) +{ + switch (g_currentMenu) + { + case MENU_MOUSEBTNS: + case MENU_MOUSEADVANCED: + case MENU_JOYSTICKBTNS: + case MENU_JOYSTICKAXIS: + mgametextcenter(origin.x, origin.y + (31<<16), "Select a function to assign"); + + Bsprintf(tempbuf, "to %s", entry->name); + + mgametextcenter(origin.x, origin.y + ((31+9)<<16), tempbuf); + + mgametextcenter(origin.x, origin.y + (161<<16), "Press \"Escape\" To Cancel"); + break; + } +} + +static int32_t Menu_PreCustom2ColScreen(MenuEntry_t *entry) +{ + if (g_currentMenu == MENU_KEYBOARDKEYS) + { + MenuCustom2Col_t *column = (MenuCustom2Col_t*)entry->entry; + + int32_t sc = KB_GetLastScanCode(); + if (sc != sc_None) + { + char key[2]; + key[0] = ud.config.KeyboardKeys[M_KEYBOARDKEYS.currentEntry][0]; + key[1] = ud.config.KeyboardKeys[M_KEYBOARDKEYS.currentEntry][1]; + + S_PlaySound(PISTOL_BODYHIT); + + *column->column[M_KEYBOARDKEYS.currentColumn] = KB_GetLastScanCode(); + + CONFIG_MapKey(M_KEYBOARDKEYS.currentEntry, ud.config.KeyboardKeys[M_KEYBOARDKEYS.currentEntry][0], key[0], ud.config.KeyboardKeys[M_KEYBOARDKEYS.currentEntry][1], key[1]); + + KB_ClearKeyDown(sc); + + return -1; + } + } + + return 0; +} + +static void Menu_PreCustom2ColScreenDraw(MenuEntry_t *entry, const vec2_t origin) +{ + if (g_currentMenu == MENU_KEYBOARDKEYS) + { + Bsprintf(tempbuf, "Press the key to assign as\n" + "%s for \"%s\"\n" + "\n" + "Press \"Escape\" To Cancel" + , M_KEYBOARDKEYS.currentColumn?"secondary":"primary", entry->name); + mgametextcenter(origin.x, origin.y + (90<<16), tempbuf); + } +} + +static void Menu_EntryFocus(/*MenuEntry_t *entry*/) +{ + switch (g_currentMenu) + { + case MENU_LOAD: + if (M_LOAD.currentEntry < (int32_t)g_nummenusaves) + { + savebrief_t & sv = g_menusaves[M_LOAD.currentEntry].brief; + if (sv.isValid()) + G_LoadSaveHeaderNew(sv.path, &savehead); + } + break; + + case MENU_SAVE: + if (0 < M_SAVE.currentEntry && M_SAVE.currentEntry <= (int32_t)g_nummenusaves) + { + savebrief_t & sv = g_menusaves[M_SAVE.currentEntry-1].brief; + if (sv.isValid()) + G_LoadSaveHeaderNew(sv.path, &savehead); + } + break; + + default: + break; + } +} + +static void Menu_StartGameWithoutSkill(void) +{ + ud.m_player_skill = M_SKILL.currentEntry+1; + + g_skillSoundVoice = S_PlaySound(PISTOL_BODYHIT); + + ud.m_respawn_monsters = 0; + + ud.m_monsters_off = ud.monsters_off = 0; + + ud.m_respawn_items = 0; + ud.m_respawn_inventory = 0; + + ud.multimode = 1; + + G_NewGame_EnterLevel(); +} + +static void Menu_DoCheat(int32_t cheatID) +{ + if (numplayers != 1 || !(g_player[myconnectindex].ps->gm & MODE_GAME)) + return; + + osdcmd_cheatsinfo_stat.cheatnum = cheatID; +} + +static int32_t Menu_Cheat_Warp(char const * const numbers) +{ + if (numplayers != 1 || !(g_player[myconnectindex].ps->gm & MODE_GAME)) + return 0; + + if (numbers == NULL || !numbers[0] || !numbers[1] || (VOLUMEALL && !numbers[2])) + return 1; + + if (VOLUMEALL) + { + osdcmd_cheatsinfo_stat.volume = numbers[0] - '0'; + osdcmd_cheatsinfo_stat.level = (numbers[1] - '0')*10+(numbers[2]-'0'); + } + else + { + osdcmd_cheatsinfo_stat.volume = numbers[0] - '0'; + osdcmd_cheatsinfo_stat.level = numbers[1] - '0'; + } + + osdcmd_cheatsinfo_stat.volume--; + osdcmd_cheatsinfo_stat.level--; + + if ((VOLUMEONE && osdcmd_cheatsinfo_stat.volume > 0) || osdcmd_cheatsinfo_stat.volume > g_volumeCnt-1 || + osdcmd_cheatsinfo_stat.level >= MAXLEVELS || g_mapInfo[osdcmd_cheatsinfo_stat.volume *MAXLEVELS+osdcmd_cheatsinfo_stat.level].filename == NULL) + return 1; + + osdcmd_cheatsinfo_stat.cheatnum = CHEAT_SCOTTY; + + return 0; +} + +static int32_t Menu_Cheat_Skill(char const * const number) +{ + if (numplayers != 1 || !(g_player[myconnectindex].ps->gm & MODE_GAME)) + return 0; + + if (number == NULL || !number[0]) + return 1; + + osdcmd_cheatsinfo_stat.volume = number[0] - '1'; + + osdcmd_cheatsinfo_stat.cheatnum = CHEAT_SKILL; + + return 0; +} + +/* +Functions where a "newValue" or similar is passed are run *before* the linked variable is actually changed. +That way you can compare the new and old values and potentially block the change. +*/ +static void Menu_EntryLinkActivate(MenuEntry_t *entry) +{ + switch (g_currentMenu) + { + case MENU_EPISODE: + if (entry != &ME_EPISODE_USERMAP) + { + ud.m_volume_number = M_EPISODE.currentEntry; + ud.m_level_number = 0; + + if (g_skillCnt == 0) + Menu_StartGameWithoutSkill(); + } + break; + + case MENU_SKILL: + { + int32_t skillsound = PISTOL_BODYHIT; + + switch (M_SKILL.currentEntry) + { + case 0: + skillsound = JIBBED_ACTOR6; + break; + case 1: + skillsound = BONUS_SPEECH1; + break; + case 2: + skillsound = DUKE_GETWEAPON2; + break; + case 3: + skillsound = JIBBED_ACTOR5; + break; + } + + ud.m_player_skill = M_SKILL.currentEntry+1; + + g_skillSoundVoice = S_PlaySound(skillsound); + + if (M_SKILL.currentEntry == 3) ud.m_respawn_monsters = 1; + else ud.m_respawn_monsters = 0; + + ud.m_monsters_off = ud.monsters_off = 0; + + ud.m_respawn_items = 0; + ud.m_respawn_inventory = 0; + + ud.multimode = 1; + + G_NewGame_EnterLevel(); + break; + } + + case MENU_JOYSTICKAXES: + M_JOYSTICKAXIS.title = getjoyname(0, M_JOYSTICKAXES.currentEntry); + MEO_JOYSTICKAXIS_ANALOG.data = &ud.config.JoystickAnalogueAxes[M_JOYSTICKAXES.currentEntry]; + MEO_JOYSTICKAXIS_SCALE.variable = &ud.config.JoystickAnalogueScale[M_JOYSTICKAXES.currentEntry]; + MEO_JOYSTICKAXIS_DEAD.variable = &ud.config.JoystickAnalogueDead[M_JOYSTICKAXES.currentEntry]; + MEO_JOYSTICKAXIS_SATU.variable = &ud.config.JoystickAnalogueSaturate[M_JOYSTICKAXES.currentEntry]; + MEO_JOYSTICKAXIS_DIGITALNEGATIVE.data = &ud.config.JoystickDigitalFunctions[M_JOYSTICKAXES.currentEntry][0]; + MEO_JOYSTICKAXIS_DIGITALPOSITIVE.data = &ud.config.JoystickDigitalFunctions[M_JOYSTICKAXES.currentEntry][1]; + break; + + case MENU_CHEATS: + { + const int32_t cheatFuncID = M_CHEATS.currentEntry - 1; + switch (cheatFuncID) + { + case -1: + case CHEATFUNC_WARP: + case CHEATFUNC_SKILL: + break; + default: + Menu_DoCheat(CheatFunctionIDs[cheatFuncID]); + break; + } + break; + } + + default: + break; + } + + if (entry == &ME_VIDEOSETUP_APPLY) + { + resolution_t p = { xdim, ydim, fullscreen, bpp, 0 }; + int32_t prend = getrendermode(); + int32_t pvsync = vsync; + + resolution_t n = { resolution[newresolution].xdim, resolution[newresolution].ydim, newfullscreen, + (newrendermode == REND_CLASSIC) ? 8 : resolution[newresolution].bppmax, 0 }; + int32_t nrend = newrendermode; + int32_t nvsync = newvsync; + + if (setgamemode(n.flags, n.xdim, n.ydim, n.bppmax) < 0) + { + if (setgamemode(p.flags, p.xdim, p.ydim, p.bppmax) < 0) + { + setrendermode(prend); + G_GameExit("Failed restoring old video mode."); + } + else + { + onvideomodechange(p.bppmax > 8); + vsync = setvsync(pvsync); + } + } + else onvideomodechange(n.bppmax > 8); + + g_restorePalette = -1; + G_UpdateScreenArea(); + setrendermode(nrend); + vsync = setvsync(nvsync); + ud.config.ScreenMode = fullscreen; + ud.config.ScreenWidth = xdim; + ud.config.ScreenHeight = ydim; + ud.config.ScreenBPP = bpp; + } + else if (entry == &ME_SOUND_RESTART) + { + ud.config.MixRate = soundrate; + ud.config.NumVoices = soundvoices; + ud.config.FluidsynthBackend = musicbackend; + + S_SoundShutdown(); + S_MusicShutdown(); + + S_MusicStartup(); + S_SoundStartup(); + + FX_StopAllSounds(); + S_ClearSoundLocks(); + + if (ud.config.MusicToggle) + S_RestartMusic(); + } + else if (entry == &ME_COLCORR_RESET) + { + vid_gamma = DEFAULT_GAMMA; + vid_contrast = DEFAULT_CONTRAST; + vid_brightness = DEFAULT_BRIGHTNESS; + ud.brightness = 0; + r_ambientlight = r_ambientlightrecip = 1.f; + setbrightness(ud.brightness>>2,g_player[myconnectindex].ps->palette,0); + } + else if (entry == &ME_KEYBOARDSETUP_RESET) + CONFIG_SetDefaultKeys(keydefaults); + else if (entry == &ME_KEYBOARDSETUP_RESETCLASSIC) + CONFIG_SetDefaultKeys(oldkeydefaults); + else if (entry == &ME_NETHOST_LAUNCH) + { + // master does whatever it wants + if (g_netServer) + { + Net_FillNewGame(&pendingnewgame, 1); + Net_StartNewGame(); + Net_SendNewGame(1, NULL); + } + else if (voting == -1) + { + Net_SendMapVoteInitiate(); + Menu_Change(MENU_NETWAITVOTES); + } + } +} + +static int32_t Menu_EntryOptionModify(MenuEntry_t *entry, int32_t newOption) +{ + int32_t x; + DukePlayer_t *ps = g_player[myconnectindex].ps; + + if (entry == &ME_GAMESETUP_DEMOREC) + { + if ((ps->gm&MODE_GAME)) + G_CloseDemoWrite(); + } +#ifdef _WIN32 + else if (entry == &ME_GAMESETUP_UPDATES) + ud.config.LastUpdateCheck = 0; +#endif + else if (entry == &ME_GAMESETUP_WEAPSWITCH_PICKUP) + { + ud.weaponswitch &= ~(1|4); + switch (newOption) + { + case 2: + ud.weaponswitch |= 4; + fallthrough__; + case 1: + ud.weaponswitch |= 1; + break; + default: + break; + } + } + else if (entry == &ME_SOUND) + { + if (newOption == 0) + { + FX_StopAllSounds(); + S_ClearSoundLocks(); + } + } + else if (entry == &ME_SOUND_MUSIC) + { + ud.config.MusicToggle = newOption; + + if (newOption == 0) + S_PauseMusic(1); + else + { + S_RestartMusic(); + S_PauseMusic(0); + } + } + else if (entry == &ME_SOUND_DUKETALK) + ud.config.VoiceToggle = (ud.config.VoiceToggle&~1) | newOption; + else if (entry == &ME_MOUSESETUP_SMOOTH) + CONTROL_SmoothMouse = ud.config.SmoothInput; + else if (entry == &ME_JOYSTICKAXIS_ANALOG) + CONTROL_MapAnalogAxis(M_JOYSTICKAXES.currentEntry, newOption, controldevice_joystick); + else if (entry == &ME_NETOPTIONS_EPISODE) + { + if (newOption < g_volumeCnt) + ud.m_volume_number = newOption; + } + else if (entry == &ME_NETOPTIONS_MONSTERS) + { + ud.m_monsters_off = (newOption == g_skillCnt); + if (newOption < g_skillCnt) + ud.m_player_skill = newOption; + } + else if (entry == &ME_ADULTMODE) + { + if (newOption) + { + for (x=0; x<g_animWallCnt; x++) + switch (DYNAMICTILEMAP(wall[animwall[x].wallnum].picnum)) + { + case FEMPIC1__STATIC: + wall[animwall[x].wallnum].picnum = BLANKSCREEN; + break; + case FEMPIC2__STATIC: + case FEMPIC3__STATIC: + wall[animwall[x].wallnum].picnum = SCREENBREAK6; + break; + } + + ud.pwlockout[0] = 0; + Menu_Change(MENU_ADULTPASSWORD); +// return -1; + } + else + { + if (ud.pwlockout[0] == 0) + { + ud.lockout = 0; +#if 0 + for (x=0; x<g_numAnimWalls; x++) + if (wall[animwall[x].wallnum].picnum != W_SCREENBREAK && + wall[animwall[x].wallnum].picnum != W_SCREENBREAK+1 && + wall[animwall[x].wallnum].picnum != W_SCREENBREAK+2) + if (wall[animwall[x].wallnum].extra >= 0) + wall[animwall[x].wallnum].picnum = wall[animwall[x].wallnum].extra; +#endif + } + else + { + Menu_Change(MENU_ADULTPASSWORD); + return -1; + } + } + } + else if (entry == &ME_VIDEOSETUP_FRAMELIMIT) + { + g_frameDelay = newOption ? (getu64tickspersec()/newOption) : 0; + } + + switch (g_currentMenu) + { + case MENU_MOUSEBTNS: + CONTROL_MapButton(newOption, MenuMouseDataIndex[M_MOUSEBTNS.currentEntry][0], MenuMouseDataIndex[M_MOUSEBTNS.currentEntry][1], controldevice_mouse); + CONTROL_FreeMouseBind(MenuMouseDataIndex[M_MOUSEBTNS.currentEntry][0]); + break; + case MENU_MOUSEADVANCED: + { + size_t i; + for (i = 0; i < ARRAY_SIZE(MEL_INTERNAL_MOUSEADVANCED_DAXES); i++) + if (entry == MEL_INTERNAL_MOUSEADVANCED_DAXES[i]) + CONTROL_MapDigitalAxis(i>>1, newOption, i&1, controldevice_mouse); + } + break; + case MENU_JOYSTICKBTNS: + CONTROL_MapButton(newOption, M_JOYSTICKBTNS.currentEntry>>1, M_JOYSTICKBTNS.currentEntry&1, controldevice_joystick); + break; + case MENU_JOYSTICKAXIS: + { + size_t i; + for (i = 0; i < ARRAY_SIZE(MEL_INTERNAL_JOYSTICKAXIS_DIGITAL); i++) + if (entry == MEL_INTERNAL_JOYSTICKAXIS_DIGITAL[i]) + CONTROL_MapDigitalAxis(i>>1, newOption, i&1, controldevice_joystick); + } + break; + } + + return 0; +} + +static void Menu_EntryOptionDidModify(MenuEntry_t *entry) +{ +#ifdef USE_OPENGL + int domodechange = 0; +#endif + + if (entry == &ME_GAMESETUP_AIM_AUTO || + entry == &ME_GAMESETUP_WEAPSWITCH_PICKUP || + entry == &ME_PLAYER_NAME || + entry == &ME_PLAYER_COLOR || + entry == &ME_PLAYER_TEAM) + G_UpdatePlayerFromMenu(); +#ifdef USE_OPENGL + else if (entry == &ME_DISPLAYSETUP_ANISOTROPY || entry == &ME_DISPLAYSETUP_TEXFILTER) + gltexapplyprops(); + else if (entry == &ME_RENDERERSETUP_TEXQUALITY) + { + texcache_invalidate(); + r_downsizevar = r_downsize; + domodechange = 1; + } +#ifdef POLYMER + else if (entry == &ME_POLYMER_LIGHTS || + entry == &ME_POLYMER_LIGHTPASSES || + entry == &ME_POLYMER_SHADOWCOUNT) + domodechange = 1; +#endif + + if (domodechange) + { + resetvideomode(); + if (setgamemode(fullscreen, xdim, ydim, bpp)) + OSD_Printf("restartvid: Reset failed...\n"); + onvideomodechange(ud.config.ScreenBPP>8); + G_RefreshLights(); + } +#endif +} + +static void Menu_Custom2ColScreen(/*MenuEntry_t *entry*/) +{ + if (g_currentMenu == MENU_KEYBOARDKEYS) + { + KB_FlushKeyboardQueue(); + KB_ClearLastScanCode(); + } +} + +static int32_t Menu_EntryRangeInt32Modify(MenuEntry_t *entry, int32_t newValue) +{ + if (entry == &ME_SCREENSETUP_SCREENSIZE) + G_SetViewportShrink(newValue - vpsize); + else if (entry == &ME_SCREENSETUP_SBARSIZE) + G_SetStatusBarScale(newValue); + else if (entry == &ME_SOUND_VOLUME_MASTER) + { + FX_SetVolume(newValue); + S_MusicVolume(MASTER_VOLUME(ud.config.MusicVolume)); + } + else if (entry == &ME_SOUND_VOLUME_MUSIC) + S_MusicVolume(MASTER_VOLUME(newValue)); + else if (entry == &ME_MOUSEADVANCED_SCALEX) + CONTROL_SetAnalogAxisScale(0, newValue, controldevice_mouse); + else if (entry == &ME_MOUSEADVANCED_SCALEY) + CONTROL_SetAnalogAxisScale(1, newValue, controldevice_mouse); + else if (entry == &ME_JOYSTICKAXIS_SCALE) + CONTROL_SetAnalogAxisScale(M_JOYSTICKAXES.currentEntry, newValue, controldevice_joystick); + else if (entry == &ME_JOYSTICKAXIS_DEAD) + setjoydeadzone(M_JOYSTICKAXES.currentEntry, newValue, *MEO_JOYSTICKAXIS_SATU.variable); + else if (entry == &ME_JOYSTICKAXIS_SATU) + setjoydeadzone(M_JOYSTICKAXES.currentEntry, *MEO_JOYSTICKAXIS_DEAD.variable, newValue); + + return 0; +} + +static int32_t Menu_EntryRangeFloatModify(MenuEntry_t *entry, float newValue) +{ +#ifndef EDUKE32_SIMPLE_MENU + if (entry == &ME_COLCORR_AMBIENT) + r_ambientlightrecip = 1.f/newValue; +#else + UNREFERENCED_PARAMETER(entry); + UNREFERENCED_PARAMETER(newValue); +#endif + + return 0; +} + +static int32_t Menu_EntryRangeFloatDidModify(MenuEntry_t *entry) +{ + if (entry == &ME_COLCORR_GAMMA) + { + ud.brightness = GAMMA_CALC<<2; + setbrightness(ud.brightness>>2, g_player[myconnectindex].ps->palette, 0); + } + else if (entry == &ME_COLCORR_CONTRAST || entry == &ME_COLCORR_BRIGHTNESS) + { + setbrightness(ud.brightness>>2, g_player[myconnectindex].ps->palette, 0); + } + + return 0; +} + +#ifdef MENU_ENABLE_RANGEDOUBLE +static int32_t Menu_EntryRangeDoubleModify(void /*MenuEntry_t *entry, double newValue*/) +{ + + return 0; +} +#endif + +static uint32_t save_xxh = 0; + +static void Menu_EntryStringActivate(/*MenuEntry_t *entry*/) +{ + switch (g_currentMenu) + { + case MENU_SAVE: + if (M_SAVE.currentEntry > 0) + { + savebrief_t & sv = g_menusaves[M_SAVE.currentEntry-1].brief; + if (!save_xxh) + save_xxh = XXH32((uint8_t *)sv.name, MAXSAVEGAMENAME, 0xDEADBEEF); + if (sv.isValid()) + Menu_Change(MENU_SAVEVERIFY); + } + else + { + ME_SAVE_NEW.name = nullptr; + save_xxh = 0; + } + break; + + default: + break; + } +} + +static int32_t Menu_EntryStringSubmit(/*MenuEntry_t *entry, */char *input) +{ + int32_t returnvar = 0; + + switch (g_currentMenu) + { + case MENU_SAVE: + { + savebrief_t & sv = g_lastusersave = M_SAVE.currentEntry == 0 ? savebrief_t{input} : g_menusaves[M_SAVE.currentEntry-1].brief; + + // dirty hack... char 127 in last position indicates an auto-filled name +#ifdef __ANDROID__ + if (1) +#else + if (input[0] == 0 || (sv.name[MAXSAVEGAMENAME] == 127 && + strncmp(sv.name, input, MAXSAVEGAMENAME) == 0 && + save_xxh == XXH32((uint8_t *)sv.name, MAXSAVEGAMENAME, 0xDEADBEEF))) +#endif + { + strncpy(sv.name, g_mapInfo[ud.volume_number * MAXLEVELS + ud.level_number].name, MAXSAVEGAMENAME); + sv.name[MAXSAVEGAMENAME] = 127; + returnvar = -1; + } + else + { + strncpy(sv.name, input, MAXSAVEGAMENAME); + sv.name[MAXSAVEGAMENAME] = 0; + } + + G_SavePlayerMaybeMulti(sv); + + g_quickload = &sv; + g_player[myconnectindex].ps->gm = MODE_GAME; + + Menu_Change(MENU_CLOSE); + save_xxh = 0; + break; + } + + default: + break; + } + + return returnvar; +} + +static void Menu_EntryStringCancel(/*MenuEntry_t *entry*/) +{ + switch (g_currentMenu) + { + case MENU_SAVE: + save_xxh = 0; + ME_SAVE_NEW.name = s_NewSaveGame; + break; + + default: + break; + } +} + +/* +This is polled when the menu code is populating the screen but for some reason doesn't have the data. +*/ +static int32_t Menu_EntryOptionSource(MenuEntry_t *entry, int32_t currentValue) +{ + if (entry == &ME_GAMESETUP_WEAPSWITCH_PICKUP) + return (ud.weaponswitch & 1) ? ((ud.weaponswitch & 4) ? 2 : 1) : 0; + else if (entry == &ME_SOUND_DUKETALK) + return ud.config.VoiceToggle & 1; + else if (entry == &ME_NETOPTIONS_MONSTERS) + return (ud.m_monsters_off ? g_skillCnt : ud.m_player_skill); + + return currentValue; +} + +static void Menu_Verify(int32_t input) +{ + switch (g_currentMenu) + { + case MENU_RESETPLAYER: + if (input) + { + KB_FlushKeyboardQueue(); + KB_ClearKeysDown(); + FX_StopAllSounds(); + S_ClearSoundLocks(); + + G_LoadPlayerMaybeMulti(*g_quickload); + } + else + { + if (sprite[g_player[myconnectindex].ps->i].extra <= 0) + { + if (G_EnterLevel(MODE_GAME)) G_BackToMenu(); + return; + } + + Menu_Change(MENU_CLOSE); + } + break; + + case MENU_LOADVERIFY: + if (input) + { + savebrief_t & sv = g_menusaves[M_LOAD.currentEntry].brief; + + if (strcmp(sv.path, g_lastusersave.path) != 0) + { + g_freshload = sv; + g_lastusersave.reset(); + g_lastautosave.reset(); + g_quickload = &g_freshload; + } + else + { + g_quickload = &g_lastusersave; + } + + KB_FlushKeyboardQueue(); + KB_ClearKeysDown(); + + Menu_Change(MENU_CLOSE); + + G_LoadPlayerMaybeMulti(sv); + } + break; + + case MENU_SAVEVERIFY: + if (!input) + { + save_xxh = 0; + + ((MenuString_t*)M_SAVE.entrylist[M_SAVE.currentEntry]->entry)->editfield = NULL; + } + break; + + case MENU_QUIT: + case MENU_QUIT_INGAME: + if (input) + G_GameQuit(); + else + g_quitDeadline = 0; + break; + + case MENU_QUITTOTITLE: + if (input) + { + g_player[myconnectindex].ps->gm = MODE_DEMO; + if (ud.recstat == 1) + G_CloseDemoWrite(); + E_MapArt_Clear(); + } + break; + + case MENU_NETWAITVOTES: + if (!input) + Net_SendMapVoteCancel(0); + break; + + default: + break; + } +} + +static int Menu_CheatStringMatch(char const * input, char const * cheat) +{ + while (*cheat || *input) + { + if (*cheat != *input) + { + if (!(*cheat == '#' && Bisdigit(*input))) + return 0; + } + + ++cheat; + ++input; + } + + return 1; +} + +static void Menu_TextFormSubmit(char *input) +{ + switch (g_currentMenu) + { + case MENU_ADULTPASSWORD: + if (Bstrlen(input) && (ud.pwlockout[0] == 0 || ud.lockout == 0)) + Bstrcpy(&ud.pwlockout[0], input); + else if (Bstrcmp(input, &ud.pwlockout[0]) == 0) + { + for (bssize_t x=0; x<g_animWallCnt; x++) + if ((unsigned) animwall[x].wallnum < (unsigned)numwalls && wall[animwall[x].wallnum].picnum != W_SCREENBREAK && + wall[animwall[x].wallnum].picnum != W_SCREENBREAK+1 && + wall[animwall[x].wallnum].picnum != W_SCREENBREAK+2) + if (wall[animwall[x].wallnum].extra >= 0) + wall[animwall[x].wallnum].picnum = wall[animwall[x].wallnum].extra; + ud.lockout = 0; + } + + S_PlaySound(PISTOL_BODYHIT); + Menu_Change(MENU_GAMESETUP); + break; + + case MENU_CHEATENTRY: + { + const size_t inputlength = Bstrlen(input); + Bstrcpy(tempbuf, input); + for (size_t i = 0; i < inputlength; i++) + tempbuf[i] = Btolower(tempbuf[i]); + + int8_t cheatID = -1; + + if (inputlength > 2 && tempbuf[0] == scantoasc[CheatKeys[0]] && tempbuf[1] == scantoasc[CheatKeys[1]]) + { + for (size_t i = 0; i < NUMCHEATS; i++) + if (Menu_CheatStringMatch(tempbuf+2, CheatStrings[i])) + { + cheatID = i; + break; + } + } + + switch (cheatID) + { + case -1: + S_PlaySound(KICK_HIT); + break; + case CHEAT_SCOTTY: + { + char const * const numberpos = Bstrchr(CheatStrings[CHEAT_SCOTTY], '#'); + if (numberpos == NULL) + { + S_PlaySound(KICK_HIT); + break; + } + + Menu_Cheat_Warp(input + (numberpos - CheatStrings[CHEAT_SCOTTY]) + 2); + if (g_player[myconnectindex].ps->gm&MODE_MENU) + S_PlaySound(DUKE_GET); + break; + } + case CHEAT_SKILL: + { + char const * const numberpos = Bstrchr(CheatStrings[CHEAT_SKILL], '#'); + if (numberpos == NULL) + { + S_PlaySound(KICK_HIT); + break; + } + + Menu_Cheat_Skill(input + (numberpos - CheatStrings[CHEAT_SKILL]) + 2); + if (g_player[myconnectindex].ps->gm&MODE_MENU) + S_PlaySound(DUKE_GET); + break; + } + default: + Menu_DoCheat(cheatID); + S_PlaySound(DUKE_GET); + break; + } + + if (cheatID >= 0) + cl_cheatmask |= CheatFunctionFlags[cheatID]; + + if ((NAM_WW2GI && (cl_cheatmask & (1<<CHEATFUNC_QUOTETODD))) || + ((cl_cheatmask & (1<<CHEATFUNC_QUOTEBETA)) && (cl_cheatmask & (1<<CHEATFUNC_QUOTETODD)) && (cl_cheatmask & (1<<CHEATFUNC_QUOTEALLEN)))) + { + S_PlaySound(DUKE_GETWEAPON6); + cl_cheatmask = ~0; + } + + Menu_Change(MENU_CHEATS); + break; + } + + case MENU_CHEAT_WARP: + if (Menu_Cheat_Warp(input)) + S_PlaySound(KICK_HIT); + Menu_Change(MENU_CHEATS); + break; + + case MENU_CHEAT_SKILL: + if (Menu_Cheat_Skill(input)) + S_PlaySound(KICK_HIT); + Menu_Change(MENU_CHEATS); + break; + + default: + break; + } +} + +void klistbookends(CACHE1D_FIND_REC *start) +{ + CACHE1D_FIND_REC *end = start, *n; + size_t i = 0; + + if (!start) + return; + + while (start->prev) + start = start->prev; + + while (end->next) + end = end->next; + + for (n = start; n; n = n->next) + { + n->type = i; // overload this... + n->usera = start; + n->userb = end; + i++; + } +} + +static void Menu_FileSelectInit(MenuFileSelect_t *object) +{ + size_t i; + + fnlist_clearnames(&object->fnlist); + + if (object->destination[0] == 0) + Bstrcpy(object->destination, "./"); + Bcorrectfilename(object->destination, 1); + + fnlist_getnames(&object->fnlist, object->destination, object->pattern, 0, 0); + object->findhigh[0] = object->fnlist.finddirs; + object->findhigh[1] = object->fnlist.findfiles; + + for (i = 0; i < 2; ++i) + { + object->scrollPos[i] = 0; + klistbookends(object->findhigh[i]); + } + + object->currentList = 0; + if (object->findhigh[1]) + object->currentList = 1; + + KB_FlushKeyboardQueue(); +} + +static void Menu_FileSelect(int32_t input) +{ + switch (g_currentMenu) + { + case MENU_NETUSERMAP: + if ((g_netServer || ud.multimode > 1)) + Net_SendUserMapName(); + fallthrough__; + case MENU_USERMAP: + if (input) + { + ud.m_volume_number = 0; + ud.m_level_number = 7; + + if (g_skillCnt > 0) + Menu_AnimateChange(MENU_SKILL, MA_Advance); + else + Menu_StartGameWithoutSkill(); + } + break; + + default: + break; + } +} + + + + + +static Menu_t* Menu_BinarySearch(MenuID_t query, size_t searchstart, size_t searchend) +{ + const size_t thissearch = (searchstart + searchend) / 2; + const MenuID_t difference = query - Menus[thissearch].menuID; + + if (difference == 0) + return &Menus[thissearch]; + else if (searchstart == searchend) + return NULL; + else if (difference > 0) + { + if (thissearch == searchend) + return NULL; + searchstart = thissearch + 1; + } + else if (difference < 0) + { + if (thissearch == searchstart) + return NULL; + searchend = thissearch - 1; + } + + return Menu_BinarySearch(query, searchstart, searchend); +} + +static Menu_t* Menu_Find(MenuID_t query) +{ + if ((unsigned) query > (unsigned) Menus[numMenus-1].menuID) + return NULL; + + return Menu_BinarySearch(query, 0, numMenus-1); +} + +static Menu_t* Menu_FindFiltered(MenuID_t query) +{ + if ((g_player[myconnectindex].ps->gm&MODE_GAME) && query == MENU_MAIN) + query = MENU_MAIN_INGAME; + + return Menu_Find(query); +} + +MenuAnimation_t m_animation; + +int32_t Menu_Anim_SinOutRight(MenuAnimation_t *animdata) +{ + return sintable[divscale10(totalclock - animdata->start, animdata->length) + 512] - 16384; +} +int32_t Menu_Anim_SinInRight(MenuAnimation_t *animdata) +{ + return sintable[divscale10(totalclock - animdata->start, animdata->length) + 512] + 16384; +} +int32_t Menu_Anim_SinOutLeft(MenuAnimation_t *animdata) +{ + return -sintable[divscale10(totalclock - animdata->start, animdata->length) + 512] + 16384; +} +int32_t Menu_Anim_SinInLeft(MenuAnimation_t *animdata) +{ + return -sintable[divscale10(totalclock - animdata->start, animdata->length) + 512] - 16384; +} + +void Menu_AnimateChange(int32_t cm, MenuAnimationType_t animtype) +{ + if (KXDWN) + { + m_animation.start = 0; + m_animation.length = 0; + Menu_Change(cm); + return; + } + + switch (animtype) + { + case MA_Advance: + { + Menu_t * const previousMenu = m_currentMenu; + + if (!Menu_Change(cm)) + { + m_animation.out = Menu_Anim_SinOutRight; + m_animation.in = Menu_Anim_SinInRight; + m_animation.start = totalclock; + m_animation.length = 30; + + m_animation.previous = previousMenu; + m_animation.current = m_currentMenu; + } + + break; + } + case MA_Return: + { + Menu_t * const previousMenu = m_currentMenu; + + if (!Menu_Change(cm)) + { + m_animation.out = Menu_Anim_SinOutLeft; + m_animation.in = Menu_Anim_SinInLeft; + m_animation.start = totalclock; + m_animation.length = 30; + + m_animation.previous = previousMenu; + m_animation.current = m_currentMenu; + } + + break; + } + default: + m_animation.start = 0; + m_animation.length = 0; + Menu_Change(cm); + break; + } +} + +static void Menu_MaybeSetSelectionToChild(Menu_t * m, MenuID_t id) +{ + if (m->type == Menu) + { + MenuMenu_t * menu = (MenuMenu_t *)m->object; + + for (size_t i = 0, i_end = menu->numEntries; i < i_end; ++i) + { + MenuEntry_t const * entry = menu->entrylist[i]; + if (entry != NULL && entry->type == Link) + { + MenuLink_t const * link = (MenuLink_t const *)entry->entry; + if (link->linkID == id) + { + menu->currentEntry = i; + Menu_AdjustForCurrentEntryAssignmentBlind(menu); + break; + } + } + } + } +} + +static void Menu_ReadSaveGameHeaders() +{ + ReadSaveGameHeaders(); + + size_t const numloaditems = max(g_nummenusaves, 1), numsaveitems = g_nummenusaves+1; + ME_LOAD = (MenuEntry_t *)realloc(ME_LOAD, g_nummenusaves * sizeof(MenuEntry_t)); + MEL_LOAD = (MenuEntry_t **)realloc(MEL_LOAD, numloaditems * sizeof(MenuEntry_t *)); + MEO_SAVE = (MenuString_t *)realloc(MEO_SAVE, g_nummenusaves * sizeof(MenuString_t)); + ME_SAVE = (MenuEntry_t *)realloc(ME_SAVE, g_nummenusaves * sizeof(MenuEntry_t)); + MEL_SAVE = (MenuEntry_t **)realloc(MEL_SAVE, numsaveitems * sizeof(MenuEntry_t *)); + + MEL_SAVE[0] = &ME_SAVE_NEW; + ME_SAVE_NEW.name = s_NewSaveGame; + for (size_t i = 0; i < g_nummenusaves; ++i) + { + MEL_LOAD[i] = &ME_LOAD[i]; + MEL_SAVE[i+1] = &ME_SAVE[i]; + ME_LOAD[i] = ME_LOAD_TEMPLATE; + ME_SAVE[i] = ME_SAVE_TEMPLATE; + ME_SAVE[i].entry = &MEO_SAVE[i]; + MEO_SAVE[i] = MEO_SAVE_TEMPLATE; + + ME_LOAD[i].name = g_menusaves[i].brief.name; + MEO_SAVE[i].variable = g_menusaves[i].brief.name; + } + + if (g_nummenusaves == 0) + MEL_LOAD[0] = &ME_LOAD_EMPTY; + + M_LOAD.entrylist = MEL_LOAD; + M_LOAD.numEntries = numloaditems; + M_SAVE.entrylist = MEL_SAVE; + M_SAVE.numEntries = numsaveitems; + + // lexicographical sorting? +} + +static void Menu_AboutToStartDisplaying(Menu_t * m) +{ + switch (m->menuID) + { + case MENU_MAIN: + if (KXDWN) + ME_MAIN_LOADGAME.name = s_Continue; + break; + + case MENU_MAIN_INGAME: + if (KXDWN) + ME_MAIN_LOADGAME.name = s_LoadGame; + break; + + case MENU_LOAD: + if (KXDWN) + M_LOAD.title = (g_player[myconnectindex].ps->gm & MODE_GAME) ? s_LoadGame : s_Continue; + + Menu_ReadSaveGameHeaders(); + + for (size_t i = 0; i < g_nummenusaves; ++i) + MenuEntry_LookDisabledOnCondition(&ME_LOAD[i], g_menusaves[i].isOldVer); + + if (g_quickload && g_quickload->isValid()) + { + for (size_t i = 0; i < g_nummenusaves; ++i) + { + if (strcmp(g_menusaves[i].brief.path, g_quickload->path) == 0) + { + M_LOAD.currentEntry = i; + Menu_AdjustForCurrentEntryAssignmentBlind(&M_LOAD); + break; + } + } + } + break; + + case MENU_SAVE: + if (g_previousMenu == MENU_SAVEVERIFY) + break; + + Menu_ReadSaveGameHeaders(); + + if (g_lastusersave.isValid()) + { + for (size_t i = 0; i < g_nummenusaves; ++i) + { + if (strcmp(g_menusaves[i].brief.path, g_lastusersave.path) == 0) + { + M_SAVE.currentEntry = i+1; + Menu_AdjustForCurrentEntryAssignmentBlind(&M_SAVE); + break; + } + } + } + + for (size_t i = 0; i < g_nummenusaves; ++i) + MenuEntry_LookDisabledOnCondition(&ME_SAVE[i], g_menusaves[i].isOldVer); + + if (g_player[myconnectindex].ps->gm&MODE_GAME) + { + g_screenCapture = 1; + G_DrawRooms(myconnectindex,65536); + g_screenCapture = 0; + } + break; + + case MENU_VIDEOSETUP: + newresolution = 0; + for (size_t i = 0; i < MAXVALIDMODES; ++i) + { + if (resolution[i].xdim == xdim && resolution[i].ydim == ydim) + { + newresolution = i; + break; + } + } + newrendermode = getrendermode(); + newfullscreen = fullscreen; + newvsync = vsync; + break; + + case MENU_ADVSOUND: + soundrate = ud.config.MixRate; + soundvoices = ud.config.NumVoices; + musicbackend = ud.config.FluidsynthBackend; + break; + + default: + break; + } + + switch (m->type) + { + case TextForm: + typebuf[0] = 0; + ((MenuTextForm_t*)m->object)->input = typebuf; + break; + case FileSelect: + Menu_FileSelectInit((MenuFileSelect_t*)m->object); + break; + case Menu: + { + MenuMenu_t *menu = (MenuMenu_t*)m->object; + // MenuEntry_t* currentry = menu->entrylist[menu->currentEntry]; + + // need this for MENU_SKILL + if (menu->currentEntry >= menu->numEntries) + menu->currentEntry = 0; + + int32_t i = menu->currentEntry; + while (!menu->entrylist[menu->currentEntry] || ((MenuEntry_t*)menu->entrylist[menu->currentEntry])->type == Spacer) + { + menu->currentEntry++; + if (menu->currentEntry >= menu->numEntries) + menu->currentEntry = 0; + if (menu->currentEntry == i) + G_GameExit("Menu_Change: Attempted to show a menu with no entries."); + } + + Menu_EntryFocus(/*currentry*/); + break; + } + default: + break; + } +} + +static void Menu_ChangingTo(Menu_t * m) +{ + switch (m->menuID) + { +#ifdef __ANDROID__ + case MENU_TOUCHBUTTONS: + AndroidToggleButtonEditor(); + break; +#endif + default: + break; + } + + switch (m->type) + { + case TextForm: + WithSDL2_StartTextInput(); + break; + default: + break; + } +} + +int Menu_Change(MenuID_t cm) +{ + Menu_t * beginMenu = m_currentMenu; + + cm = VM_OnEventWithReturn(EVENT_CHANGEMENU, g_player[screenpeek].ps->i, screenpeek, cm); + + if (cm == MENU_PREVIOUS) + { + m_currentMenu = m_previousMenu; + g_currentMenu = g_previousMenu; + } + else if (cm == MENU_CLOSE) + Menu_Close(myconnectindex); + else if (cm >= 0) + { + Menu_t * search = Menu_FindFiltered(cm); + + if (search == NULL) + return 0; // intentional, so that users don't use any random value as "don't change" + + m_previousMenu = m_currentMenu; + g_previousMenu = g_currentMenu; + m_currentMenu = search; + g_currentMenu = search->menuID; + } + else + return 1; + + if (KXDWN) + { + Menu_t * parent = m_currentMenu, * result = NULL; + + while (parent != NULL && parent->menuID != MENU_OPTIONS && parent->menuID != MENU_MAIN && parent->menuID != MENU_MAIN_INGAME) + { + result = parent = Menu_FindFiltered(parent->parentID); + } + + m_parentMenu = result; + + if (result) + { + Menu_MaybeSetSelectionToChild(result, m_currentMenu->menuID); + Menu_AboutToStartDisplaying(result); + } + } + + Menu_MaybeSetSelectionToChild(m_currentMenu, beginMenu->menuID); + Menu_AboutToStartDisplaying(m_currentMenu); + Menu_ChangingTo(m_currentMenu); + +#if !defined EDUKE32_TOUCH_DEVICES + m_menuchange_watchpoint = 1; +#endif + + return 0; +} + + + + + + + + + + +void G_CheckPlayerColor(int32_t *color, int32_t prev_color) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(MEOSV_PLAYER_COLOR); ++i) + if (*color == MEOSV_PLAYER_COLOR[i]) + return; + + *color = prev_color; +} + + +int32_t Menu_DetermineSpecialState(MenuEntry_t *entry) +{ + if (entry == NULL) + return 0; + + if (entry->type == String) + { + if (((MenuString_t*)entry->entry)->editfield) + return 1; + } + else if (entry->type == Option) + { + if (((MenuOption_t*)entry->entry)->options->currentEntry >= 0) + return 2; + } + else if (entry->type == Custom2Col) + { + if (((MenuCustom2Col_t*)entry->entry)->screenOpen) + return 2; + } + + return 0; +} + +int32_t Menu_IsTextInput(Menu_t *cm) +{ + switch (m_currentMenu->type) + { + case Verify: + case TextForm: + case FileSelect: + case Message: + return 1; + break; + case Panel: + return 0; + break; + case Menu: + { + MenuMenu_t *menu = (MenuMenu_t*)cm->object; + MenuEntry_t *entry = menu->entrylist[menu->currentEntry]; + return Menu_DetermineSpecialState(entry); + } + break; + } + + return 0; +} + +static inline int32_t Menu_BlackTranslucentBackgroundOK(MenuID_t cm) +{ + switch (cm) + { + case MENU_COLCORR: + case MENU_COLCORR_INGAME: + return 0; + break; + default: + return 1; + break; + } + + return 1; +} + +static inline int32_t Menu_UpdateScreenOK(MenuID_t cm) +{ + switch (cm) + { + case MENU_LOAD: + case MENU_SAVE: + case MENU_LOADVERIFY: + case MENU_SAVEVERIFY: + return 0; + break; + default: + return 1; + break; + } + + return 1; +} + + +/* + Code below this point is entirely general, + so if you want to change or add a menu, + chances are you should scroll up. +*/ + +int32_t m_mouselastactivity; +#if !defined EDUKE32_TOUCH_DEVICES +int32_t m_mousewake_watchpoint, m_menuchange_watchpoint; +#endif +int32_t m_mousecaught; +static vec2_t m_prevmousepos, m_mousepos, m_mousedownpos; + +void Menu_Open(size_t playerID) +{ + g_player[playerID].ps->gm |= MODE_MENU; + + readmouseabsxy(&m_prevmousepos, &mouseabs); + m_mouselastactivity = -M_MOUSETIMEOUT; + +#if !defined EDUKE32_TOUCH_DEVICES + m_mousewake_watchpoint = 0; +#endif + + AppGrabMouse(0); +} + +void Menu_Close(size_t playerID) +{ + if (g_player[playerID].ps->gm & MODE_GAME) + { + // The following lines are here so that you cannot close the menu when no game is running. + g_player[playerID].ps->gm &= ~MODE_MENU; + AppGrabMouse(1); + + if ((!g_netServer && ud.multimode < 2) && ud.recstat != 2) + { + ready2send = 1; + totalclock = ototalclock; + CAMERACLOCK = totalclock; + CAMERADIST = 65536; + m_animation.start = 0; + m_animation.length = 0; + + // Reset next-viewscreen-redraw counter. + // XXX: are there any other cases like that in need of handling? + if (g_curViewscreen >= 0) + actor[g_curViewscreen].t_data[0] = totalclock; + } + + walock[TILE_SAVESHOT] = 199; + G_UpdateScreenArea(); + } +} + +static int32_t x_widescreen_left(void) +{ + return (320<<15) - scale(240<<15, xdim, ydim); +} + +static int32_t xdim_from_320_16(int32_t x) +{ + const int32_t screenwidth = scale(240<<16, xdim, ydim); + return scale(x + (screenwidth>>1) - (160<<16), xdim, screenwidth); +} +static int32_t ydim_from_200_16(int32_t y) +{ + return scale(y, ydim, 200<<16); +} + +static void Menu_BlackRectangle(int32_t x, int32_t y, int32_t width, int32_t height, int32_t orientation) +{ + const int32_t xscale = divscale16(width, tilesiz[0].x<<16), yscale = divscale16(height, tilesiz[0].y<<16); + + rotatesprite_(x, y, max(xscale, yscale), 0, 0, 127, 4, (orientation&(1|32))|2|8|16, 0, 0, xdim_from_320_16(x), ydim_from_200_16(y), xdim_from_320_16(x + width), ydim_from_200_16(y + height)); +} + +enum MenuTextFlags_t +{ + MT_Selected = 1<<0, + MT_Disabled = 1<<1, + MT_XCenter = 1<<2, + MT_XRight = 1<<3, + MT_YCenter = 1<<4, + MT_Literal = 1<<5, +}; + +static void Menu_GetFmt(const MenuFont_t *font, uint8_t const status, int32_t *s, int32_t *p, int32_t *z) +{ + if (KXDWN) *z = (status & MT_Selected) ? *z + (*z >> 4) : *z; + *s = (status & MT_Selected) ? (sintable[(totalclock<<5)&2047]>>12) : font->shade_deselected; + *p = (status & MT_Disabled) ? font->pal_disabled : font->pal; +} + +static vec2_t Menu_Text(int32_t x, int32_t y, const MenuFont_t *font, const char *t, uint8_t status, int32_t ydim_upper, int32_t ydim_lower) +{ + int32_t s, p, ybetween = font->between.y; + int32_t f = font->textflags; + if (status & MT_XCenter) + f |= TEXT_XCENTER; + if (status & MT_XRight) + f |= TEXT_XRIGHT; + if (status & MT_YCenter) + { + f |= TEXT_YCENTER | TEXT_YOFFSETZERO; + ybetween = font->emptychar.y; // <^ the battle against 'Q' + } + if (status & MT_Literal) + f |= TEXT_LITERALESCAPE; + + int32_t z = font->zoom; + + Menu_GetFmt(font, status, &s, &p, &z); + + return G_ScreenText(font->tilenum, x, y, z, 0, 0, t, s, p, 2|8|16|ROTATESPRITE_FULL16, 0, font->emptychar.x, font->emptychar.y, font->between.x, ybetween, f, 0, ydim_upper, xdim-1, ydim_lower); +} + +#if 0 +static vec2_t Menu_TextSize(int32_t x, int32_t y, const MenuFont_t *font, const char *t, uint8_t status) +{ + int32_t f = font->textflags; + if (status & MT_Literal) + f |= TEXT_LITERALESCAPE; + + return G_ScreenTextSize(font->tilenum, x, y, font->zoom, 0, t, 2|8|16|ROTATESPRITE_FULL16, font->emptychar.x, font->emptychar.y, font->between.x, font->between.y, f, 0, 0, xdim-1, ydim-1); +} +#endif + +static int32_t Menu_FindOptionBinarySearch(MenuOption_t *object, const int32_t query, size_t searchstart, size_t searchend) +{ + const size_t thissearch = (searchstart + searchend) / 2; + const bool isIdentityMap = object->options->optionValues == NULL; + + const int32_t destination = isIdentityMap ? (int32_t) thissearch : object->options->optionValues[thissearch]; + + const int32_t difference = query - destination; + + Bassert(!isIdentityMap || query >= 0); + + if (difference == 0) + return thissearch; + else if (searchstart == searchend) + return -1; + else if (difference > 0) + { + if (thissearch == searchend) + return -1; + searchstart = thissearch + 1; + } + else if (difference < 0) + { + if (thissearch == searchstart) + return -1; + searchend = thissearch - 1; + } + + return Menu_FindOptionBinarySearch(object, query, searchstart, searchend); +} + +static int32_t Menu_MouseOutsideBounds(vec2_t const * const pos, const int32_t x, const int32_t y, const int32_t width, const int32_t height) +{ + return pos->x < x || pos->x >= x + width || pos->y < y || pos->y >= y + height; +} + +static void Menu_RunScrollbar(Menu_t *cm, MenuMenuFormat_t const * const format, const int32_t totalextent, int32_t * const scrollPos, const int32_t rightedge, const vec2_t origin) +{ + if (totalextent > klabs(format->bottomcutoff)) + { + const int32_t scrollx = origin.x + rightedge - (tilesiz[SELECTDIR].x<<16), scrolly = origin.y + format->pos.y; + const int32_t scrollwidth = tilesiz[SELECTDIR].x<<16; + const int32_t scrollheight = klabs(format->bottomcutoff) - format->pos.y; + const int32_t scrollregionheight = scrollheight - (tilesiz[SELECTDIR].y<<16); + const int32_t scrollPosMax = totalextent - klabs(format->bottomcutoff); + + Menu_BlackRectangle(scrollx, scrolly, scrollwidth, scrollheight, 1|32); + + rotatesprite_fs(scrollx, scrolly + scale(scrollregionheight, *scrollPos, scrollPosMax), 65536, 0, SELECTDIR, 0, 0, 26); + + if (cm == m_currentMenu && !m_mousecaught && MOUSEACTIVECONDITIONAL(mousepressstate == Mouse_Pressed || mousepressstate == Mouse_Held)) + { + const int32_t scrolltilehalfheight = tilesiz[SELECTDIR].y<<15; + const int32_t scrollregiony = scrolly + scrolltilehalfheight; + + // region between the y-midline of the arrow at the extremes scrolls proportionally + if (!Menu_MouseOutsideBounds(&m_mousepos, scrollx, scrollregiony, scrollwidth, scrollregionheight)) + { + *scrollPos = scale(m_mousepos.y - scrollregiony, scrollPosMax, scrollregionheight); + + m_mousecaught = 1; + } + // region outside the y-midlines clamps to the extremes + else if (!Menu_MouseOutsideBounds(&m_mousepos, scrollx, scrolly, scrollwidth, scrollheight)) + { + if (m_mousepos.y > scrolly + scrollheight/2) + *scrollPos = scrollPosMax; + else + *scrollPos = 0; + + m_mousecaught = 1; + } + } + } +} + +typedef enum MenuMovement_t +{ + MM_Up = 1, + MM_End = 3, + MM_Down = 4, + MM_Home = 12, + MM_Left = 16, + MM_AllTheWayLeft = 48, + MM_Right = 64, + MM_AllTheWayRight = 192, + MM_Swap = 80, +} MenuMovement_t; + +static MenuEntry_t *Menu_RunInput_Menu_MovementVerify(MenuMenu_t *menu); +static MenuEntry_t *Menu_RunInput_Menu_Movement(MenuMenu_t *menu, MenuMovement_t direction); +static void Menu_RunInput_EntryLink_Activate(MenuEntry_t *entry); +static void Menu_RunInput_EntryOptionList_MovementVerify(MenuOption_t *object); +static void Menu_RunInput_EntryOptionList_Movement(MenuOption_t *object, MenuMovement_t direction); +static int32_t Menu_RunInput_EntryOption_Modify(MenuEntry_t *entry, MenuOption_t *object, int32_t newValueIndex); +static int32_t Menu_RunInput_EntryOption_Movement(MenuEntry_t *entry, MenuOption_t *object, MenuMovement_t direction); +static int32_t Menu_RunInput_EntryOption_Activate(MenuEntry_t *entry, MenuOption_t *object); +static int32_t Menu_RunInput_EntryOptionList_Activate(MenuEntry_t *entry, MenuOption_t *object); +static void Menu_RunInput_EntryCustom2Col_Activate(MenuEntry_t *entry); +static void Menu_RunInput_EntryRangeInt32_MovementVerify(MenuEntry_t *entry, MenuRangeInt32_t *object, int32_t newValue); +static void Menu_RunInput_EntryRangeInt32_MovementArbitrary(MenuEntry_t *entry, MenuRangeInt32_t *object, int32_t newValue); +static void Menu_RunInput_EntryRangeInt32_Movement(MenuEntry_t *entry, MenuRangeInt32_t *object, MenuMovement_t direction); +static void Menu_RunInput_EntryRangeFloat_MovementVerify(MenuEntry_t *entry, MenuRangeFloat_t *object, float newValue); +static void Menu_RunInput_EntryRangeFloat_MovementArbitrary(MenuEntry_t *entry, MenuRangeFloat_t *object, float newValue); +static void Menu_RunInput_EntryRangeFloat_Movement(MenuEntry_t *entry, MenuRangeFloat_t *object, MenuMovement_t direction); +#ifdef MENU_ENABLE_RANGEDOUBLE +static void Menu_RunInput_EntryRangeDouble_MovementVerify(/*MenuEntry_t *entry, */MenuRangeDouble_t *object, double newValue); +static void Menu_RunInput_EntryRangeDouble_MovementArbitrary(/*MenuEntry_t *entry, */MenuRangeDouble_t *object, double newValue); +static void Menu_RunInput_EntryRangeDouble_Movement(/*MenuEntry_t *entry, */MenuRangeDouble_t *object, MenuMovement_t direction); +#endif +static void Menu_RunInput_EntryString_Activate(MenuEntry_t *entry); +static void Menu_RunInput_EntryString_Submit(/*MenuEntry_t *entry, */MenuString_t *object); +static void Menu_RunInput_EntryString_Cancel(/*MenuEntry_t *entry, */MenuString_t *object); +static void Menu_RunInput_FileSelect_MovementVerify(MenuFileSelect_t *object); +static void Menu_RunInput_FileSelect_Movement(MenuFileSelect_t *object, MenuMovement_t direction); +static void Menu_RunInput_FileSelect_Select(MenuFileSelect_t *object); + +static int32_t M_RunMenu_Menu(Menu_t *cm, MenuMenu_t *menu, MenuEntry_t *currentry, int32_t state, const vec2_t origin, bool actually_draw) +{ + int32_t totalHeight = 0; + + // RIP MenuGroup_t b. 2014-03-?? d. 2014-11-29 + { + int32_t e; + const int32_t y_upper = menu->format->pos.y; + const int32_t y_lower = klabs(menu->format->bottomcutoff); + int32_t y = 0; + int32_t calculatedentryspacing = 0; + + if (menu->format->bottomcutoff < 0) + { + int32_t totalheight = 0, numvalidentries = 0; + + for (e = 0; e < menu->numEntries; ++e) + { + MenuEntry_t *entry = menu->entrylist[e]; + + if (entry == NULL) + continue; + + ++numvalidentries; + + // assumes height == font->get_yline()! + totalheight += entry->getHeight(); + } + + calculatedentryspacing = (klabs(menu->format->bottomcutoff) - menu->format->pos.y - totalheight) / (numvalidentries > 1 ? numvalidentries - 1 : 1); + } + + // totalHeight calculating pass + for (e = 0; e < menu->numEntries; ++e) + { + MenuEntry_t *entry = menu->entrylist[e]; + + if (entry == NULL) + continue; + + int32_t const height = entry->getHeight(); + + y += height; + totalHeight = y; + y += (!calculatedentryspacing || calculatedentryspacing > entry->getMarginBottom()) ? entry->getMarginBottom() : calculatedentryspacing; + } + y = 0; + + int32_t ydim_upper, ydim_lower; + if (y_upper + totalHeight > y_lower) + { + ydim_upper = ydim_from_200_16(origin.y + y_upper); + ydim_lower = ydim_from_200_16(origin.y + y_lower); + } + else + { + ydim_upper = 0; + ydim_lower = ydim-1; + } + + for (e = 0; e < menu->numEntries; ++e) + { + MenuEntry_t *entry = menu->entrylist[e]; + + if (entry == NULL) + continue; + + int32_t x = menu->format->pos.x + entry->getIndent(); + + uint8_t status = 0; + if (e == menu->currentEntry) + status |= MT_Selected; + if (entry->flags & (MEF_Disabled|MEF_LookDisabled)) + status |= MT_Disabled; + if (entry->format->width == 0) + status |= MT_XCenter; + + bool const dodraw = entry->type != Spacer && actually_draw && + 0 <= y - menu->scrollPos + entry->font->get_yline() && + y - menu->scrollPos <= klabs(menu->format->bottomcutoff) - menu->format->pos.y; + + int32_t const height = entry->getHeight(); // max(textsize.y, entry->font->get_yline()); // bluefont Q ruins this + status |= MT_YCenter; + int32_t const y_internal = origin.y + y_upper + y + (height>>1) - menu->scrollPos; + + vec2_t textsize; + if (dodraw) + textsize = Menu_Text(origin.x + x, y_internal, entry->font, entry->name, status, ydim_upper, ydim_lower); + + if (entry->format->width < 0) + status |= MT_XRight; + + if (dodraw && (status & MT_Selected) && state != 1) + { + if (status & MT_XCenter) + { + Menu_DrawCursorLeft(origin.x + (MENU_MARGIN_CENTER<<16) + entry->font->cursorCenterPosition, y_internal, entry->font->cursorScale); + Menu_DrawCursorRight(origin.x + (MENU_MARGIN_CENTER<<16) - entry->font->cursorCenterPosition, y_internal, entry->font->cursorScale); + } + else + Menu_DrawCursorLeft(origin.x + x - entry->font->cursorLeftPosition, y_internal, entry->font->cursorScale); + } + + // need this up here to avoid race conditions + entry->ybottom = (entry->ytop = y_upper + y) + height; + + if (dodraw) + { + const int32_t mousex = origin.x + entry->format->width == 0 ? x - textsize.x/2 : x; + const int32_t mousey = origin.y + y_upper + y - menu->scrollPos; + int32_t mousewidth = entry->format->width == 0 ? textsize.x : klabs(entry->format->width); + + if (entry->name) + x += klabs(entry->format->width); + + switch (entry->type) + { + case Spacer: + break; + case Dummy: + if (MOUSEACTIVECONDITIONAL(state != 1 && cm == m_currentMenu && !Menu_MouseOutsideBounds(&m_mousepos, mousex, mousey, mousewidth, height))) + { + if (MOUSEWATCHPOINTCONDITIONAL(Menu_MouseOutsideBounds(&m_prevmousepos, mousex, mousey, mousewidth, height))) + { + menu->currentEntry = e; + Menu_RunInput_Menu_MovementVerify(menu); + } + } + break; + case Link: + if (MOUSEACTIVECONDITIONAL(state != 1 && cm == m_currentMenu && !Menu_MouseOutsideBounds(&m_mousepos, mousex, mousey, mousewidth, height))) + { + if (MOUSEWATCHPOINTCONDITIONAL(Menu_MouseOutsideBounds(&m_prevmousepos, mousex, mousey, mousewidth, height))) + { + menu->currentEntry = e; + Menu_RunInput_Menu_MovementVerify(menu); + } + + if (!m_mousecaught && mousepressstate == Mouse_Released && !Menu_MouseOutsideBounds(&m_mousedownpos, mousex, mousey, mousewidth, entry->font->get_yline())) + { + menu->currentEntry = e; + Menu_RunInput_Menu_MovementVerify(menu); + + if (entry->flags & MEF_Disabled) + break; + + Menu_RunInput_EntryLink_Activate(entry); + + if (g_player[myconnectindex].ps->gm&MODE_MENU) // for skill selection + S_PlaySound(PISTOL_BODYHIT); + + m_mousecaught = 1; + } + } + break; + case Option: + { + MenuOption_t *object = (MenuOption_t*)entry->entry; + int32_t currentOption = Menu_FindOptionBinarySearch(object, object->data == NULL ? Menu_EntryOptionSource(entry, object->currentOption) : *object->data, 0, object->options->numOptions); + + if (currentOption >= 0) + object->currentOption = currentOption; + + int32_t optiontextx = origin.x + x; + const int32_t optiontexty = origin.y + y_upper + y - menu->scrollPos; + + const vec2_t optiontextsize = Menu_Text(optiontextx, optiontexty + (height>>1), object->font, + currentOption < 0 ? MenuCustom : currentOption < object->options->numOptions ? object->options->optionNames[currentOption] : NULL, + status, ydim_upper, ydim_lower); + + if (entry->format->width > 0) + mousewidth += optiontextsize.x; + else + optiontextx -= optiontextsize.x; + + if (MOUSEACTIVECONDITIONAL(state != 1 && cm == m_currentMenu && !Menu_MouseOutsideBounds(&m_mousepos, mousex, mousey, mousewidth, height))) + { + if (MOUSEWATCHPOINTCONDITIONAL(Menu_MouseOutsideBounds(&m_prevmousepos, mousex, mousey, mousewidth, height))) + { + menu->currentEntry = e; + Menu_RunInput_Menu_MovementVerify(menu); + } + + if (!m_mousecaught && mousepressstate == Mouse_Released && !Menu_MouseOutsideBounds(&m_mousedownpos, mousex, mousey, mousewidth, entry->font->get_yline())) + { + menu->currentEntry = e; + Menu_RunInput_Menu_MovementVerify(menu); + + if (entry->flags & MEF_Disabled) + break; + + Menu_RunInput_EntryOption_Activate(entry, object); + + S_PlaySound(PISTOL_BODYHIT); + + m_mousecaught = 1; + } + } + + break; + } + case Custom2Col: + { + MenuCustom2Col_t *object = (MenuCustom2Col_t*)entry->entry; + int32_t columnx[2] = { origin.x + x - ((status & MT_XRight) ? object->columnWidth : 0), origin.x + x + ((status & MT_XRight) ? 0 : object->columnWidth) }; + const int32_t columny = origin.y + y_upper + y - menu->scrollPos; + + const vec2_t column0textsize = Menu_Text(columnx[0], columny + (height>>1), object->font, object->key[*object->column[0]], menu->currentColumn == 0 ? status : (status & ~MT_Selected), ydim_upper, ydim_lower); + const vec2_t column1textsize = Menu_Text(columnx[1], columny + (height>>1), object->font, object->key[*object->column[1]], menu->currentColumn == 1 ? status : (status & ~MT_Selected), ydim_upper, ydim_lower); + + if (entry->format->width > 0) + mousewidth += object->columnWidth + column1textsize.x; + else + { + columnx[0] -= column0textsize.x; + columnx[1] -= column0textsize.x; + } + + if (MOUSEACTIVECONDITIONAL(state != 1 && cm == m_currentMenu && !Menu_MouseOutsideBounds(&m_mousepos, mousex, mousey, mousewidth, height))) + { + if (MOUSEWATCHPOINTCONDITIONAL(Menu_MouseOutsideBounds(&m_prevmousepos, mousex, mousey, mousewidth, height))) + { + menu->currentEntry = e; + Menu_RunInput_Menu_MovementVerify(menu); + } + + if (!Menu_MouseOutsideBounds(&m_mousepos, columnx[1], mousey, column1textsize.x, object->font->get_yline())) + { + if (MOUSEWATCHPOINTCONDITIONAL(Menu_MouseOutsideBounds(&m_prevmousepos, columnx[1], mousey, column1textsize.x, object->font->get_yline()))) + { + menu->currentColumn = 1; + } + + if (!m_mousecaught && mousepressstate == Mouse_Released && !Menu_MouseOutsideBounds(&m_mousedownpos, columnx[1], mousey, column1textsize.x, object->font->get_yline())) + { + menu->currentEntry = e; + Menu_RunInput_Menu_MovementVerify(menu); + menu->currentColumn = 1; + + if (entry->flags & MEF_Disabled) + break; + + Menu_RunInput_EntryCustom2Col_Activate(entry); + + S_PlaySound(PISTOL_BODYHIT); + + m_mousecaught = 1; + } + } + else if (!Menu_MouseOutsideBounds(&m_mousepos, columnx[0], mousey, column0textsize.x, object->font->get_yline())) + { + if (MOUSEWATCHPOINTCONDITIONAL(Menu_MouseOutsideBounds(&m_prevmousepos, columnx[0], mousey, column0textsize.x, object->font->get_yline()))) + { + menu->currentColumn = 0; + } + + if (!m_mousecaught && mousepressstate == Mouse_Released && !Menu_MouseOutsideBounds(&m_mousedownpos, columnx[0], mousey, column0textsize.x, object->font->get_yline())) + { + menu->currentEntry = e; + Menu_RunInput_Menu_MovementVerify(menu); + menu->currentColumn = 0; + + if (entry->flags & MEF_Disabled) + break; + + Menu_RunInput_EntryCustom2Col_Activate(entry); + + S_PlaySound(PISTOL_BODYHIT); + + m_mousecaught = 1; + } + } + } + break; + } + case RangeInt32: + { + MenuRangeInt32_t *object = (MenuRangeInt32_t*)entry->entry; + + int32_t s, p; + int32_t z = entry->font->cursorScale; + Menu_GetFmt(object->font, status, &s, &p, &z); + + const int32_t slidebarwidth = mulscale16(tilesiz[SLIDEBAR].x<<16, z); + const int32_t slidebarheight = mulscale16(tilesiz[SLIDEBAR].y<<16, z); + + if (status & MT_XRight) + x -= slidebarwidth; + else + mousewidth += slidebarwidth; + + const int32_t slidebarx = origin.x + x; + const int32_t slidebary = origin.y + y_upper + y + ((height - slidebarheight)>>1) - menu->scrollPos; + + rotatesprite_ybounds(slidebarx, slidebary, z, 0, SLIDEBAR, s, (entry->flags & (MEF_Disabled|MEF_LookDisabled)) ? 1 : 0, 2|8|16|ROTATESPRITE_FULL16, ydim_upper, ydim_lower); + + const int32_t slideregionwidth = mulscale16((tilesiz[SLIDEBAR].x-2-tilesiz[SLIDEBAR+1].x)<<16, z); + const int32_t slidepointx = slidebarx + (1<<16) + scale(slideregionwidth, *object->variable - object->min, object->max - object->min); + const int32_t slidepointy = slidebary + mulscale16((tilesiz[SLIDEBAR].y-tilesiz[SLIDEBAR+1].y)<<15, z); + + rotatesprite_ybounds(slidepointx, slidepointy, z, 0, SLIDEBAR+1, s, (entry->flags & (MEF_Disabled|MEF_LookDisabled)) ? 1 : 0, 2|8|16|ROTATESPRITE_FULL16, ydim_upper, ydim_lower); + + if (object->flags & DisplayTypeMask) + { + status |= MT_XRight; + + int32_t onehundredpercent = object->onehundredpercent; + if (onehundredpercent == 0) + onehundredpercent = object->max; + + switch (object->flags & DisplayTypeMask) + { + case DisplayTypeInteger: + Bsprintf(tempbuf, "%d", *object->variable); + break; + case DisplayTypePercent: + Bsprintf(tempbuf, "%d%%", roundscale(*object->variable, 100, onehundredpercent)); + break; + case DisplayTypeNormalizedDecimal: + Bsprintf(tempbuf, "%.2f", (double) *object->variable / (double) onehundredpercent); + break; + } + + Menu_Text(origin.x + x - (4<<16), origin.y + y_upper + y + (height>>1) - menu->scrollPos, object->font, tempbuf, status, ydim_upper, ydim_lower); + } + + if (MOUSEACTIVECONDITIONAL(state != 1 && cm == m_currentMenu && !Menu_MouseOutsideBounds(&m_mousepos, mousex, mousey, mousewidth, height))) + { + if (MOUSEWATCHPOINTCONDITIONAL(Menu_MouseOutsideBounds(&m_prevmousepos, mousex, mousey, mousewidth, height))) + { + menu->currentEntry = e; + Menu_RunInput_Menu_MovementVerify(menu); + } + + if (!m_mousecaught && (mousepressstate == Mouse_Pressed || mousepressstate == Mouse_Held)) + { + const int32_t slidepointhalfwidth = mulscale16((2+tilesiz[SLIDEBAR+1].x)<<15, z); + const int32_t slideregionx = slidebarx + slidepointhalfwidth; + + menu->currentEntry = e; + Menu_RunInput_Menu_MovementVerify(menu); + + if (entry->flags & MEF_Disabled) + break; + + // region between the x-midline of the slidepoint at the extremes slides proportionally + if (!Menu_MouseOutsideBounds(&m_mousepos, slideregionx, mousey, slideregionwidth, height)) + { + Menu_RunInput_EntryRangeInt32_MovementArbitrary(entry, object, roundscale(object->max - object->min, m_mousepos.x - slideregionx, slideregionwidth) + object->min); + + m_mousecaught = 1; + } + // region outside the x-midlines clamps to the extremes + else if (!Menu_MouseOutsideBounds(&m_mousepos, slidebarx, mousey, slidebarwidth, height)) + { + if (m_mousepos.x > slideregionx + slideregionwidth/2) + Menu_RunInput_EntryRangeInt32_MovementVerify(entry, object, object->max); + else + Menu_RunInput_EntryRangeInt32_MovementVerify(entry, object, object->min); + + m_mousecaught = 1; + } + } + } + + break; + } + case RangeFloat: + { + MenuRangeFloat_t *object = (MenuRangeFloat_t*)entry->entry; + + int32_t s, p; + int32_t z = entry->font->cursorScale; + Menu_GetFmt(object->font, status, &s, &p, &z); + + const int32_t slidebarwidth = mulscale16(tilesiz[SLIDEBAR].x<<16, z); + const int32_t slidebarheight = mulscale16(tilesiz[SLIDEBAR].y<<16, z); + + if (status & MT_XRight) + x -= slidebarwidth; + else + mousewidth += slidebarwidth; + + const int32_t slidebarx = origin.x + x; + const int32_t slidebary = origin.y + y_upper + y + ((height - slidebarheight)>>1) - menu->scrollPos; + + rotatesprite_ybounds(slidebarx, slidebary, z, 0, SLIDEBAR, s, (entry->flags & (MEF_Disabled|MEF_LookDisabled)) ? 1 : 0, 2|8|16|ROTATESPRITE_FULL16, ydim_upper, ydim_lower); + + const int32_t slideregionwidth = mulscale16((tilesiz[SLIDEBAR].x-2-tilesiz[SLIDEBAR+1].x)<<16, z); + const int32_t slidepointx = slidebarx + (1<<16) + Blrintf((float) slideregionwidth * (*object->variable - object->min) / (object->max - object->min)); + const int32_t slidepointy = slidebary + mulscale16((tilesiz[SLIDEBAR].y-tilesiz[SLIDEBAR+1].y)<<15, z); + + rotatesprite_ybounds(slidepointx, slidepointy, z, 0, SLIDEBAR+1, s, (entry->flags & (MEF_Disabled|MEF_LookDisabled)) ? 1 : 0, 2|8|16|ROTATESPRITE_FULL16, ydim_upper, ydim_lower); + + if (object->flags & DisplayTypeMask) + { + status |= MT_XRight; + + float onehundredpercent = object->onehundredpercent; + if (onehundredpercent == 0.f) + onehundredpercent = 1.f; + + switch (object->flags & DisplayTypeMask) + { + case DisplayTypeInteger: + Bsprintf(tempbuf, "%.2f", *object->variable); + break; + case DisplayTypePercent: + Bsprintf(tempbuf, "%ld%%", lrintf(*object->variable * 100.f / onehundredpercent)); + break; + case DisplayTypeNormalizedDecimal: + Bsprintf(tempbuf, "%.2f", *object->variable / onehundredpercent); + break; + } + + Menu_Text(origin.x + x - (4<<16), origin.y + y_upper + y + (height>>1) - menu->scrollPos, object->font, tempbuf, status, ydim_upper, ydim_lower); + } + + if (MOUSEACTIVECONDITIONAL(state != 1 && cm == m_currentMenu && !Menu_MouseOutsideBounds(&m_mousepos, mousex, mousey, mousewidth, height))) + { + if (MOUSEWATCHPOINTCONDITIONAL(Menu_MouseOutsideBounds(&m_prevmousepos, mousex, mousey, mousewidth, height))) + { + menu->currentEntry = e; + Menu_RunInput_Menu_MovementVerify(menu); + } + + if (!m_mousecaught && (mousepressstate == Mouse_Pressed || mousepressstate == Mouse_Held)) + { + const int32_t slidepointhalfwidth = mulscale16((2+tilesiz[SLIDEBAR+1].x)<<15, z); + const int32_t slideregionx = slidebarx + slidepointhalfwidth; + + menu->currentEntry = e; + Menu_RunInput_Menu_MovementVerify(menu); + + if (entry->flags & MEF_Disabled) + break; + + // region between the x-midline of the slidepoint at the extremes slides proportionally + if (!Menu_MouseOutsideBounds(&m_mousepos, slideregionx, mousey, slideregionwidth, height)) + { + Menu_RunInput_EntryRangeFloat_MovementArbitrary(entry, object, (object->max - object->min) * (m_mousepos.x - slideregionx) / slideregionwidth + object->min); + + m_mousecaught = 1; + } + // region outside the x-midlines clamps to the extremes + else if (!Menu_MouseOutsideBounds(&m_mousepos, slidebarx, mousey, slidebarwidth, height)) + { + if (m_mousepos.x > slideregionx + slideregionwidth/2) + Menu_RunInput_EntryRangeFloat_MovementVerify(entry, object, object->max); + else + Menu_RunInput_EntryRangeFloat_MovementVerify(entry, object, object->min); + + m_mousecaught = 1; + } + } + } + + break; + } +#ifdef MENU_ENABLE_RANGEDOUBLE + case RangeDouble: + { + MenuRangeDouble_t *object = (MenuRangeDouble_t*)entry->entry; + + int32_t s, p; + int32_t z = entry->font->cursorScale; + Menu_GetFmt(object->font, status, &s, &p, &z); + + const int32_t slidebarwidth = mulscale16(tilesiz[SLIDEBAR].x<<16, z); + const int32_t slidebarheight = mulscale16(tilesiz[SLIDEBAR].y<<16, z); + + if (status & MT_XRight) + x -= slidebarwidth; + else + mousewidth += slidebarwidth; + + const int32_t slidebarx = origin.x + x; + const int32_t slidebary = origin.y + y_upper + y + ((height - slidebarheight)>>1) - menu->scrollPos; + + rotatesprite_ybounds(slidebarx, slidebary, z, 0, SLIDEBAR, s, (entry->flags & (MEF_Disabled|MEF_LookDisabled)) ? 1 : 0, 2|8|16|ROTATESPRITE_FULL16, ydim_upper, ydim_lower); + + const int32_t slideregionwidth = mulscale16((tilesiz[SLIDEBAR].x-2-tilesiz[SLIDEBAR+1].x)<<16, z); + const int32_t slidepointx = slidebarx + (1<<16) + lrint((double) slideregionwidth * (*object->variable - object->min) / (object->max - object->min)); + const int32_t slidepointy = slidebary + mulscale16((tilesiz[SLIDEBAR].y-tilesiz[SLIDEBAR+1].y)<<15, z); + + rotatesprite_ybounds(slidepointx, slidepointy, z, 0, SLIDEBAR+1, s, (entry->flags & (MEF_Disabled|MEF_LookDisabled)) ? 1 : 0, 2|8|16|ROTATESPRITE_FULL16, ydim_upper, ydim_lower); + + if (object->flags & DisplayTypeMask) + { + status |= MT_XRight; + + double onehundredpercent = object->onehundredpercent; + if (onehundredpercent == 0.) + onehundredpercent = 1.; + + switch (object->flags & DisplayTypeMask) + { + case DisplayTypeInteger: + Bsprintf(tempbuf, "%.2f", *object->variable); + break; + case DisplayTypePercent: + Bsprintf(tempbuf, "%ld%%", lrint(*object->variable * 100. / onehundredpercent)); + break; + case DisplayTypeNormalizedDecimal: + Bsprintf(tempbuf, "%.2f", *object->variable / onehundredpercent); + break; + } + + Menu_Text(origin.x + x - (4<<16), origin.y + y_upper + y + (height>>1) - menu->scrollPos, object->font, tempbuf, status, ydim_upper, ydim_lower); + } + + if (MOUSEACTIVECONDITIONAL(state != 1 && cm == m_currentMenu && !Menu_MouseOutsideBounds(&m_mousepos, mousex, mousey, mousewidth, height))) + { + if (MOUSEWATCHPOINTCONDITIONAL(Menu_MouseOutsideBounds(&m_prevmousepos, mousex, mousey, mousewidth, height))) + { + menu->currentEntry = e; + Menu_RunInput_Menu_MovementVerify(menu); + } + + if (!m_mousecaught && (mousepressstate == Mouse_Pressed || mousepressstate == Mouse_Held)) + { + const int32_t slidepointhalfwidth = mulscale16((2+tilesiz[SLIDEBAR+1].x)<<15, z); + const int32_t slideregionx = slidebarx + slidepointhalfwidth; + + menu->currentEntry = e; + Menu_RunInput_Menu_MovementVerify(menu); + + if (entry->flags & MEF_Disabled) + break; + + // region between the x-midline of the slidepoint at the extremes slides proportionally + if (!Menu_MouseOutsideBounds(&m_mousepos, slideregionx, mousey, slideregionwidth, height)) + { + Menu_RunInput_EntryRangeDouble_MovementArbitrary(/*entry, */object, (object->max - object->min) * (m_mousepos.x - slideregionx) / slideregionwidth + object->min); + + m_mousecaught = 1; + } + // region outside the x-midlines clamps to the extremes + else if (!Menu_MouseOutsideBounds(&m_mousepos, slidebarx, mousey, slidebarwidth, height)) + { + if (m_mousepos.x > slideregionx + slideregionwidth/2) + Menu_RunInput_EntryRangeDouble_MovementVerify(/*entry, */object, object->max); + else + Menu_RunInput_EntryRangeDouble_MovementVerify(/*entry, */object, object->min); + + m_mousecaught = 1; + } + } + } + + break; + } +#endif + case String: + { + MenuString_t *object = (MenuString_t*)entry->entry; + + vec2_t dim; + int32_t stringx = x; + const int32_t stringy = origin.y + y_upper + y + (height>>1) - menu->scrollPos; + int32_t h; + + if (entry == currentry && object->editfield != NULL) + { + dim = Menu_Text(origin.x + stringx, stringy, object->font, object->editfield, (status & ~MT_Disabled) | MT_Literal, ydim_upper, ydim_lower); + h = max(dim.y, entry->font->get_yline()); + + Menu_DrawCursorText(origin.x + x + dim.x + (1<<16), stringy, h, ydim_upper, ydim_lower); + } + else + { + dim = Menu_Text(origin.x + stringx, stringy, object->font, object->variable, status, ydim_upper, ydim_lower); + h = max(dim.y, entry->font->get_yline()); + } + + if (entry->format->width > 0) + { + if (entry->name) + mousewidth += dim.x; + } + else + stringx -= dim.x; + + if (MOUSEACTIVECONDITIONAL(cm == m_currentMenu && !Menu_MouseOutsideBounds(&m_mousepos, mousex, mousey, mousewidth, h))) + { + if (state != 1 && Menu_MouseOutsideBounds(&m_prevmousepos, mousex, mousey, mousewidth, h)) + { + menu->currentEntry = e; + Menu_RunInput_Menu_MovementVerify(menu); + } + + #ifndef EDUKE32_TOUCH_DEVICES + if (!m_mousecaught && mousepressstate == Mouse_Released && !Menu_MouseOutsideBounds(&m_mousepos, mousex, mousey, mousewidth, h) && !Menu_MouseOutsideBounds(&m_mousedownpos, mousex, mousey, mousewidth, h)) + #endif + { + if (entry == currentry && object->editfield != NULL) + { + Menu_RunInput_EntryString_Submit(/*entry, */object); + + S_PlaySound(PISTOL_BODYHIT); + + m_mousecaught = 1; + } + else if (state != 1) + { + menu->currentEntry = e; + Menu_RunInput_Menu_MovementVerify(menu); + + if (entry->flags & MEF_Disabled) + break; + + Menu_RunInput_EntryString_Activate(entry); + + S_PlaySound(PISTOL_BODYHIT); + + m_mousecaught = 1; + } + } + } + + break; + } + } + } + + // prepare for the next line + y += height; + y += (!calculatedentryspacing || calculatedentryspacing > entry->getMarginBottom()) ? entry->getMarginBottom() : calculatedentryspacing; + } + + // draw indicators if applicable + if (actually_draw) + Menu_RunScrollbar(cm, menu->format, y_upper + totalHeight, &menu->scrollPos, 320<<16, origin); + } + + return totalHeight; +} + +static void Menu_RunOptionList(Menu_t *cm, MenuEntry_t *entry, MenuOption_t *object, const vec2_t origin) +{ + int32_t e, y = 0; + const int32_t y_upper = object->options->menuFormat->pos.y; + const int32_t y_lower = object->options->menuFormat->bottomcutoff; + int32_t calculatedentryspacing = object->options->getMarginBottom(); + + // assumes height == font->get_yline()! + if (calculatedentryspacing < 0) + calculatedentryspacing = (-calculatedentryspacing - object->options->font->get_yline()) / (object->options->numOptions - 1) - object->options->font->get_yline(); + + int32_t totalHeight = 0; + for (e = 0; e < object->options->numOptions; ++e) + { + int32_t const height = object->options->font->get_yline(); + + y += height; + totalHeight = y; + y += calculatedentryspacing; + } + y = 0; + + int32_t ydim_upper, ydim_lower; + if (y_upper + totalHeight > y_lower) + { + ydim_upper = ydim_from_200_16(origin.y + y_upper); + ydim_lower = ydim_from_200_16(origin.y + y_lower); + } + else + { + ydim_upper = 0; + ydim_lower = ydim-1; + } + + for (e = 0; e < object->options->numOptions; ++e) + { + int32_t const x = object->options->menuFormat->pos.x; + + uint8_t status = 0; + if (e == object->options->currentEntry) + status |= MT_Selected; + if (object->options->entryFormat->width == 0) + status |= MT_XCenter; + + bool const dodraw = 0 <= y - object->options->scrollPos + object->options->font->get_yline() && + y - object->options->scrollPos <= object->options->menuFormat->bottomcutoff - object->options->menuFormat->pos.y; + + int32_t const height = object->options->font->get_yline(); // max(textsize.y, object->options->font->get_yline()); + status |= MT_YCenter; + int32_t const y_internal = origin.y + y_upper + y + (height>>1) - object->options->scrollPos; + + vec2_t textsize; + if (dodraw) + textsize = Menu_Text(origin.x + x, y_internal, object->options->font, object->options->optionNames[e], status, ydim_upper, ydim_lower); + + if (object->options->entryFormat->width < 0) + status |= MT_XRight; + + if (dodraw && (status & MT_Selected)) + { + if (status & MT_XCenter) + { + Menu_DrawCursorLeft(origin.x + (MENU_MARGIN_CENTER<<16) + object->options->font->cursorCenterPosition, y_internal, object->options->font->cursorScale); + Menu_DrawCursorRight(origin.x + (MENU_MARGIN_CENTER<<16) - object->options->font->cursorCenterPosition, y_internal, object->options->font->cursorScale); + } + else + Menu_DrawCursorLeft(origin.x + x - object->options->font->cursorLeftPosition, y_internal, object->options->font->cursorScale); + } + + if (dodraw) + { + const int32_t mousex = origin.x + object->options->entryFormat->width == 0 ? x - textsize.x/2 : x; + const int32_t mousey = origin.y + y_upper + y - object->options->scrollPos; + const int32_t mousewidth = object->options->entryFormat->width == 0 ? textsize.x : klabs(object->options->entryFormat->width); + + if (MOUSEACTIVECONDITIONAL(cm == m_currentMenu && !Menu_MouseOutsideBounds(&m_mousepos, mousex, mousey, mousewidth, object->options->font->get_yline()))) + { + if (MOUSEWATCHPOINTCONDITIONAL(Menu_MouseOutsideBounds(&m_prevmousepos, mousex, mousey, mousewidth, object->options->font->get_yline()))) + { + object->options->currentEntry = e; + } + + if (!m_mousecaught && mousepressstate == Mouse_Released && !Menu_MouseOutsideBounds(&m_mousedownpos, mousex, mousey, mousewidth, object->options->font->get_yline())) + { + object->options->currentEntry = e; + + if (!Menu_RunInput_EntryOptionList_Activate(entry, object)) + S_PlaySound(PISTOL_BODYHIT); + + m_mousecaught = 1; + } + } + } + + // prepare for the next line + y += height; + y += calculatedentryspacing; + } + + // draw indicators if applicable + Menu_RunScrollbar(cm, object->options->menuFormat, y_upper + totalHeight, &object->options->scrollPos, 320<<16, origin); +} + +static int32_t Menu_RunInput_MouseAdvance(void) +{ + return MOUSEACTIVECONDITIONAL(!m_mousecaught && mousepressstate == Mouse_Released); +} + +static int32_t Menu_RunInput_MouseReturn_status; + +#if !defined EDUKE32_TOUCH_DEVICES +static void Menu_Run_MouseReturn(Menu_t *cm, const vec2_t origin) +{ + if (!MOUSEACTIVECONDITION) + return; + + if (cm->menuID == MENU_MAIN) + return; + + rotatesprite_(origin.x + (tilesiz[SELECTDIR].y << 16), 0, 65536, 512, SELECTDIR, + Menu_RunInput_MouseReturn_status ? 4 - (sintable[(totalclock << 4) & 2047] >> 11) : 6, 0, + 2 | 8 | 16 | RS_ALIGN_L, MOUSEALPHA, 0, xdim_from_320_16(origin.x + x_widescreen_left()), 0, + xdim_from_320_16(origin.x + x_widescreen_left() + (tilesiz[SELECTDIR].y << 15)), ydim - 1); +} +#endif + +static int32_t Menu_RunInput_MouseReturn(void) +{ +#if !defined EDUKE32_TOUCH_DEVICES + if (!MOUSEACTIVECONDITION) + { + Menu_RunInput_MouseReturn_status = 0; + return 0; + } +#endif + + if (g_currentMenu == MENU_MAIN) + return 0; + + const int32_t MouseReturnRegionX = x_widescreen_left(); + + if (!Menu_MouseOutsideBounds(&m_mousepos, MouseReturnRegionX, 0, tilesiz[SELECTDIR].y<<15, tilesiz[SELECTDIR].x<<16)) + { +#if !defined EDUKE32_TOUCH_DEVICES + Menu_RunInput_MouseReturn_status = 1; +#else + Menu_RunInput_MouseReturn_status = (mousepressstate == Mouse_Pressed || mousepressstate == Mouse_Held); +#endif + + return !m_mousecaught && mousepressstate == Mouse_Released && !Menu_MouseOutsideBounds(&m_mousedownpos, MouseReturnRegionX, 0, tilesiz[SELECTDIR].y<<15, tilesiz[SELECTDIR].x<<16); + } + + Menu_RunInput_MouseReturn_status = 0; + + return 0; +} + +static void Menu_Run_AbbreviateNameIntoBuffer(const char* name, int32_t entrylength) +{ + int32_t len = Bstrlen(name); + Bstrncpy(tempbuf, name, len); + if (len > entrylength) + { + len = entrylength-3; + tempbuf[len] = 0; + while (len < entrylength) + tempbuf[len++] = '.'; + } + tempbuf[len] = 0; +} + +static void Menu_Recurse(MenuID_t cm, const vec2_t origin) +{ + switch (cm) + { + case MENU_LOADVERIFY: + case MENU_SAVEVERIFY: + case MENU_ADULTPASSWORD: + case MENU_CHEATENTRY: + case MENU_CHEAT_WARP: + case MENU_CHEAT_SKILL: + Menu_Run(m_previousMenu, origin); + break; + default: + break; + } +} + +static void Menu_Run(Menu_t *cm, const vec2_t origin) +{ + Menu_Recurse(cm->menuID, origin); + + switch (cm->type) + { + case Verify: + { + MenuVerify_t *object = (MenuVerify_t*)cm->object; + + Menu_Pre(cm->menuID); + + Menu_PreDrawBackground(cm->menuID, origin); + + Menu_PreDraw(cm->menuID, NULL, origin); + + Menu_DrawCursorLeft(origin.x + object->cursorpos.x, origin.y + object->cursorpos.y, 65536); + + break; + } + + case Message: + { + MenuMessage_t *object = (MenuMessage_t*)cm->object; + + Menu_Pre(cm->menuID); + + Menu_PreDrawBackground(cm->menuID, origin); + + Menu_PreDraw(cm->menuID, NULL, origin); + + Menu_DrawCursorLeft(origin.x + object->cursorpos.x, origin.y + object->cursorpos.y, 65536); + + break; + } + + case TextForm: + { + MenuTextForm_t *object = (MenuTextForm_t*)cm->object; + + Menu_Pre(cm->menuID); + + Menu_PreDrawBackground(cm->menuID, origin); + + Menu_BlackRectangle(origin.x + (60<<16), origin.y + (86<<16), 200<<16, 28<<16, 0); + + mgametextcenter(origin.x, origin.y + (98<<16), object->instructions, TEXT_YBOTTOM); + + const char *displaytext = object->input; + + if (object->flags & MTF_Password) + { + size_t x; + for (x = 0; x < Bstrlen(object->input); ++x) + tempbuf[x] = '*'; + tempbuf[x] = 0; + + displaytext = tempbuf; + } + + const vec2_t textreturn = mgametextcenter(origin.x, origin.y + (102<<16), displaytext); + + Menu_PreDraw(cm->menuID, NULL, origin); + + int32_t const h = MF_Bluefont.get_yline(); + + Menu_DrawCursorText(origin.x + (MENU_MARGIN_CENTER<<16) + (textreturn.x>>1) + (1<<16), origin.y + (102<<16) + (h>>1), h); + + break; + } + + case FileSelect: + { + MenuFileSelect_t *object = (MenuFileSelect_t*)cm->object; + const int32_t MenuFileSelect_scrollbar_rightedge[2] = { 160<<16, 284<<16 }; + int32_t i, selected = 0; + + Menu_Pre(cm->menuID); + + Menu_PreDrawBackground(cm->menuID, origin); + + if (object->title != NoTitle) + Menu_DrawTopBar(origin); + + + // black translucent background underneath file lists + Menu_BlackRectangle(origin.x + (36<<16), origin.y + (42<<16), 248<<16, 123<<16, 1|32); + + // path + Bsnprintf(tempbuf, sizeof(tempbuf), "Path: %s", object->destination); + Menu_Text(origin.x + object->format[0]->pos.x, origin.y + (32<<16) + (MF_Bluefont.get_yline()>>1), &MF_Bluefont, tempbuf, MT_YCenter, 0, ydim-1); + + for (i = 0; i < 2; ++i) + { + if (object->findhigh[i]) + { + CACHE1D_FIND_REC *dir; + int32_t y = 0; + const int32_t y_upper = object->format[i]->pos.y; + const int32_t y_lower = klabs(object->format[i]->bottomcutoff); + + int32_t totalHeight = 0; + for (dir = object->findhigh[i]->usera; dir; dir = dir->next) + { + y += object->font[i]->get_yline(); + totalHeight = y; + y += object->getMarginBottom(i); + } + y = 0; + + int32_t ydim_upper, ydim_lower; + if (y_upper + totalHeight > y_lower) + { + ydim_upper = ydim_from_200_16(origin.y + y_upper); + ydim_lower = ydim_from_200_16(origin.y + y_lower); + } + else + { + ydim_upper = 0; + ydim_lower = ydim-1; + } + + for (dir = object->findhigh[i]->usera; dir; dir = dir->next) + { + uint8_t status = (dir == object->findhigh[i] && object->currentList == i) ? MT_Selected : 0; + + // pal = dir->source==CACHE1D_SOURCE_ZIP ? 8 : 2 + + Menu_Run_AbbreviateNameIntoBuffer(dir->name, USERMAPENTRYLENGTH); + + const int32_t thisx = object->format[i]->pos.x; + const int32_t thisy = y - object->scrollPos[i]; + + int32_t const height = object->font[i]->get_yline(); + + if (0 <= thisy + height && thisy <= klabs(object->format[i]->bottomcutoff) - object->format[i]->pos.y) + { + status |= MT_YCenter; + + const int32_t mousex = origin.x + thisx; + const int32_t mousey = origin.y + y_upper + thisy + (height>>1); + + vec2_t textdim = Menu_Text(mousex, mousey, object->font[i], tempbuf, status, ydim_upper, ydim_lower); + + if (MOUSEACTIVECONDITIONAL(cm == m_currentMenu && !Menu_MouseOutsideBounds(&m_mousepos, mousex, mousey, textdim.x, object->font[i]->get_yline()))) + { + if (MOUSEWATCHPOINTCONDITIONAL(Menu_MouseOutsideBounds(&m_prevmousepos, mousex, mousey, textdim.x, object->font[i]->get_yline()))) + { + object->findhigh[i] = dir; + object->currentList = i; + + Menu_RunInput_FileSelect_MovementVerify(object); + } + + if (!m_mousecaught && mousepressstate == Mouse_Released && !Menu_MouseOutsideBounds(&m_mousedownpos, mousex, mousey, textdim.x, object->font[i]->get_yline())) + { + object->findhigh[i] = dir; + object->currentList = i; + + Menu_RunInput_FileSelect_MovementVerify(object); + + m_mousecaught = 1; + selected = 1; + } + } + } + + y += object->font[i]->get_yline() + object->getMarginBottom(i); + } + + Menu_RunScrollbar(cm, object->format[i], y_upper + totalHeight, &object->scrollPos[i], MenuFileSelect_scrollbar_rightedge[i], origin); + } + } + + Menu_PreDraw(cm->menuID, NULL, origin); + + if (object->title != NoTitle) + Menu_DrawTopBarCaption(object->title, origin); + + if (selected) + { + Menu_RunInput_FileSelect_Select(object); + + S_PlaySound(PISTOL_BODYHIT); + + m_mousecaught = 1; + } + + break; + } + + case Panel: + { + MenuPanel_t *object = (MenuPanel_t*)cm->object; + + Menu_Pre(cm->menuID); + + Menu_PreDrawBackground(cm->menuID, origin); + + if (object->title != NoTitle) + Menu_DrawTopBar(origin); + + Menu_PreDraw(cm->menuID, NULL, origin); + + if (object->title != NoTitle) + Menu_DrawTopBarCaption(object->title, origin); + + break; + } + + case Menu: + { + int32_t state; + + MenuMenu_t *menu = (MenuMenu_t*)cm->object; + MenuEntry_t *currentry = menu->entrylist[menu->currentEntry]; + + state = Menu_DetermineSpecialState(currentry); + + if (state != 2) + { + Menu_Pre(cm->menuID); + + Menu_PreDrawBackground(cm->menuID, origin); + + if (menu->title != NoTitle) + Menu_DrawTopBar(origin); + + Menu_PreDraw(cm->menuID, currentry, origin); + + M_RunMenu_Menu(cm, menu, currentry, state, origin); + } + else + { + Menu_PreDrawBackground(cm->menuID, origin); + + if (menu->title != NoTitle) + Menu_DrawTopBar(origin); + + if (currentry->type == Option) + { + if (currentry->name) + Menu_DrawTopBarCaption(currentry->name, origin); + + Menu_PreOptionListDraw(currentry, origin); + + Menu_RunOptionList(cm, currentry, (MenuOption_t*)currentry->entry, origin); + } + else if (currentry->type == Custom2Col) + { + Menu_PreCustom2ColScreenDraw(currentry, origin); + } + } + + if ((currentry->type != Option || state != 2) && menu->title != NoTitle) + Menu_DrawTopBarCaption(menu->title, origin); + + break; + } + } + +#if !defined EDUKE32_TOUCH_DEVICES + Menu_Run_MouseReturn(cm, origin); +#endif +} + +/* +Note: When menus are exposed to scripting, care will need to be taken so that +a user cannot define an empty MenuEntryList, or one containing only spacers, +or else this function will recurse infinitely. +*/ +static MenuEntry_t *Menu_RunInput_Menu_MovementVerify(MenuMenu_t *menu) +{ + return Menu_AdjustForCurrentEntryAssignment(menu); +} + +static MenuEntry_t *Menu_RunInput_Menu_Movement(MenuMenu_t *menu, MenuMovement_t direction) +{ + if (menu->numEntries == 1) + return menu->entrylist[menu->currentEntry]; + + switch (direction) + { + case MM_End: + menu->currentEntry = menu->numEntries; + fallthrough__; + case MM_Up: + do + { + --menu->currentEntry; + if (menu->currentEntry < 0) + return Menu_RunInput_Menu_Movement(menu, MM_End); + } + while (!menu->entrylist[menu->currentEntry] || menu->entrylist[menu->currentEntry]->type == Spacer); + break; + + case MM_Home: + menu->currentEntry = -1; + fallthrough__; + case MM_Down: + do + { + ++menu->currentEntry; + if (menu->currentEntry >= menu->numEntries) + return Menu_RunInput_Menu_Movement(menu, MM_Home); + } + while (!menu->entrylist[menu->currentEntry] || menu->entrylist[menu->currentEntry]->type == Spacer); + break; + + case MM_Swap: + menu->currentColumn = !menu->currentColumn; + break; + + default: + break; + } + + return Menu_RunInput_Menu_MovementVerify(menu); +} + +static void Menu_RunInput_EntryLink_Activate(MenuEntry_t *entry) +{ + MenuLink_t *link = (MenuLink_t*)entry->entry; + + Menu_EntryLinkActivate(entry); + + Menu_AnimateChange(link->linkID, link->animation); +} + +static void Menu_RunInput_EntryOptionList_MovementVerify(MenuOption_t *object) +{ + const int32_t listytop = object->options->menuFormat->pos.y; + // assumes height == font->get_yline()! + const int32_t unitheight = object->options->getMarginBottom() < 0 ? (-object->options->getMarginBottom() - object->options->font->get_yline()) / object->options->numOptions : (object->options->font->get_yline() + object->options->getMarginBottom()); + const int32_t ytop = listytop + object->options->currentEntry * unitheight; + const int32_t ybottom = ytop + object->options->font->get_yline(); + + if (ybottom - object->options->scrollPos > object->options->menuFormat->bottomcutoff) + object->options->scrollPos = ybottom - object->options->menuFormat->bottomcutoff; + else if (ytop - object->options->scrollPos < listytop) + object->options->scrollPos = ytop - listytop; +} + +static void Menu_RunInput_EntryOptionList_Movement(MenuOption_t *object, MenuMovement_t direction) +{ + switch (direction) + { + case MM_Up: + --object->options->currentEntry; + if (object->options->currentEntry >= 0) + break; + fallthrough__; + case MM_End: + object->options->currentEntry = object->options->numOptions-1; + break; + + case MM_Down: + ++object->options->currentEntry; + if (object->options->currentEntry < object->options->numOptions) + break; + fallthrough__; + case MM_Home: + object->options->currentEntry = 0; + break; + + default: + break; + } + + Menu_RunInput_EntryOptionList_MovementVerify(object); +} + +static int32_t Menu_RunInput_EntryOption_Modify(MenuEntry_t *entry, MenuOption_t *object, int32_t newValueIndex) +{ + int32_t newValue = (object->options->optionValues == NULL) ? newValueIndex : object->options->optionValues[newValueIndex]; + if (!Menu_EntryOptionModify(entry, newValue)) + { + object->currentOption = newValueIndex; + + if ((int32_t*)object->data != NULL) // NULL implies the functions will handle it + *((int32_t*)object->data) = newValue; + + Menu_EntryOptionDidModify(entry); + + return 0; + } + + return -1; +} + +static int32_t Menu_RunInput_EntryOption_Movement(MenuEntry_t *entry, MenuOption_t *object, MenuMovement_t direction) +{ + int32_t newValueIndex = object->currentOption; + + switch (direction) + { + case MM_Left: + --newValueIndex; + if (newValueIndex >= 0) + break; + fallthrough__; + case MM_AllTheWayRight: + newValueIndex = object->options->numOptions-1; + break; + + case MM_Right: + ++newValueIndex; + if (newValueIndex < object->options->numOptions) + break; + fallthrough__; + case MM_AllTheWayLeft: + newValueIndex = 0; + break; + + default: + break; + } + + return Menu_RunInput_EntryOption_Modify(entry, object, newValueIndex); +} + +static int32_t Menu_RunInput_EntryOption_Activate(MenuEntry_t *entry, MenuOption_t *object) +{ + if (object->options->features & 2) + return Menu_RunInput_EntryOption_Movement(entry, object, MM_Right); + else + { + object->options->currentEntry = object->currentOption >= 0 ? object->currentOption : 0; + Menu_RunInput_EntryOptionList_MovementVerify(object); + + return 0; + } +} + +static int32_t Menu_RunInput_EntryOptionList_Activate(MenuEntry_t *entry, MenuOption_t *object) +{ + if (!Menu_RunInput_EntryOption_Modify(entry, object, object->options->currentEntry)) + { + object->options->currentEntry = -1; + + return 0; + } + + return -1; +} + +static void Menu_RunInput_EntryCustom2Col_Activate(MenuEntry_t *entry) +{ + MenuCustom2Col_t *object = (MenuCustom2Col_t*)entry->entry; + + Menu_Custom2ColScreen(/*entry*/); + + object->screenOpen = 1; +} + +static void Menu_RunInput_EntryRangeInt32_MovementVerify(MenuEntry_t *entry, MenuRangeInt32_t *object, int32_t newValue) +{ + if (!Menu_EntryRangeInt32Modify(entry, newValue)) + *object->variable = newValue; +} + +static void Menu_RunInput_EntryRangeInt32_MovementArbitrary(MenuEntry_t *entry, MenuRangeInt32_t *object, int32_t newValue) +{ + if (object->flags & EnforceIntervals) + { + int32_t const range = object->max - object->min; + int32_t const maxInterval = object->steps - 1; + int32_t const newValueIndex = roundscale(newValue - object->min, maxInterval, range); + newValue = newValueIndex * range / maxInterval + object->min; + } + + Menu_RunInput_EntryRangeInt32_MovementVerify(entry, object, newValue); +} + +static void Menu_RunInput_EntryRangeInt32_Movement(MenuEntry_t *entry, MenuRangeInt32_t *object, MenuMovement_t direction) +{ + int32_t const oldValue = *object->variable; + int32_t const range = object->max - object->min; + int32_t const maxInterval = object->steps - 1; + int32_t newValueIndex = roundscale(oldValue - object->min, maxInterval, range); + int32_t const newValueProjected = newValueIndex * range / maxInterval + object->min; + + switch (direction) + { + case MM_Left: + if (newValueProjected >= oldValue) + --newValueIndex; + if (newValueIndex >= 0) + break; + fallthrough__; + case MM_AllTheWayLeft: + newValueIndex = 0; + break; + + case MM_Right: + if (newValueProjected <= oldValue) + ++newValueIndex; + if (newValueIndex <= maxInterval) + break; + fallthrough__; + case MM_AllTheWayRight: + newValueIndex = maxInterval; + break; + + default: + break; + } + + int32_t const newValue = newValueIndex * range / maxInterval + object->min; + Menu_RunInput_EntryRangeInt32_MovementVerify(entry, object, newValue); +} + +static void Menu_RunInput_EntryRangeFloat_MovementVerify(MenuEntry_t *entry, MenuRangeFloat_t *object, float newValue) +{ + if (!Menu_EntryRangeFloatModify(entry, newValue)) + { + *object->variable = newValue; + Menu_EntryRangeFloatDidModify(entry); + } +} + +static void Menu_RunInput_EntryRangeFloat_MovementArbitrary(MenuEntry_t *entry, MenuRangeFloat_t *object, float newValue) +{ + if (object->flags & EnforceIntervals) + { + float const range = object->max - object->min; + float const maxInterval = (float)(object->steps - 1); + float const newValueIndex = rintf((newValue - object->min) * maxInterval / range); + newValue = newValueIndex * range / maxInterval + object->min; + } + + Menu_RunInput_EntryRangeFloat_MovementVerify(entry, object, newValue); +} + +static void Menu_RunInput_EntryRangeFloat_Movement(MenuEntry_t *entry, MenuRangeFloat_t *object, MenuMovement_t direction) +{ + float const oldValue = *object->variable; + float const range = object->max - object->min; + float const maxInterval = (float)(object->steps - 1); + float newValueIndex = rintf((oldValue - object->min) * maxInterval / range); + float const newValueProjected = newValueIndex * range / maxInterval + object->min; + + switch (direction) + { + case MM_Left: + if (newValueProjected >= oldValue) + newValueIndex -= 1.f; + if (newValueIndex >= 0.f) + break; + fallthrough__; + case MM_AllTheWayLeft: + newValueIndex = 0.f; + break; + + case MM_Right: + if (newValueProjected <= oldValue) + newValueIndex += 1.f; + if (newValueIndex <= maxInterval) + break; + fallthrough__; + case MM_AllTheWayRight: + newValueIndex = maxInterval; + break; + + default: + break; + } + + float const newValue = newValueIndex * range / maxInterval + object->min; + Menu_RunInput_EntryRangeFloat_MovementVerify(entry, object, newValue); +} + +#ifdef MENU_ENABLE_RANGEDOUBLE +static void Menu_RunInput_EntryRangeDouble_MovementVerify(/*MenuEntry_t *entry, */MenuRangeDouble_t *object, double newValue) +{ + if (!Menu_EntryRangeDoubleModify(/*entry, newValue*/)) + *object->variable = newValue; +} + +static void Menu_RunInput_EntryRangeDouble_MovementArbitrary(/*MenuEntry_t *entry, */MenuRangeDouble_t *object, double newValue) +{ + if (object->flags & EnforceIntervals) + { + double const range = object->max - object->min; + double const maxInterval = object->steps - 1; + double const newValueIndex = rint((newValue - object->min) * maxInterval / range); + newValue = newValueIndex * range / maxInterval + object->min; + } + + Menu_RunInput_EntryRangeDouble_MovementVerify(/*entry, */object, newValue); +} + +static void Menu_RunInput_EntryRangeDouble_Movement(/*MenuEntry_t *entry, */MenuRangeDouble_t *object, MenuMovement_t direction) +{ + double const oldValue = *object->variable; + double const range = object->max - object->min; + double const maxInterval = object->steps - 1; + double newValueIndex = rint((oldValue - object->min) * maxInterval / range); + double const newValueProjected = newValueIndex * range / maxInterval + object->min; + + switch (direction) + { + case MM_Left: + if (newValueProjected >= oldValue) + newValueIndex -= 1.; + if (newValueIndex >= 0.) + break; + fallthrough__; + case MM_AllTheWayLeft: + newValueIndex = 0.; + break; + + case MM_Right: + if (newValueProjected <= oldValue) + newValueIndex += 1.; + if (newValueIndex <= maxInterval) + break; + fallthrough__; + case MM_AllTheWayRight: + newValueIndex = maxInterval; + break; + + default: + break; + } + + double const newValue = newValueIndex * range / maxInterval + object->min; + Menu_RunInput_EntryRangeDouble_MovementVerify(/*entry, */object, newValue); +} +#endif + +static void Menu_RunInput_EntryString_Activate(MenuEntry_t *entry) +{ + MenuString_t *object = (MenuString_t*)entry->entry; + + if (object->variable) + strncpy(typebuf, object->variable, TYPEBUFSIZE); + else + typebuf[0] = '\0'; + object->editfield = typebuf; + + // this limitation is an arbitrary implementation detail + if (object->bufsize > TYPEBUFSIZE) + object->bufsize = TYPEBUFSIZE; + + Menu_EntryStringActivate(/*entry*/); + WithSDL2_StartTextInput(); +} + +static void Menu_RunInput_EntryString_Submit(/*MenuEntry_t *entry, */MenuString_t *object) +{ + if (!Menu_EntryStringSubmit(/*entry, */object->editfield)) + { + if (object->variable) + strncpy(object->variable, object->editfield, object->bufsize); + } + + object->editfield = NULL; + WithSDL2_StopTextInput(); +} + +static void Menu_RunInput_EntryString_Cancel(/*MenuEntry_t *entry, */MenuString_t *object) +{ + Menu_EntryStringCancel(/*entry*/); + + object->editfield = NULL; + WithSDL2_StopTextInput(); +} + +static void Menu_RunInput_FileSelect_MovementVerify(MenuFileSelect_t *object) +{ + const int32_t listytop = object->format[object->currentList]->pos.y; + const int32_t listybottom = klabs(object->format[object->currentList]->bottomcutoff); + const int32_t ytop = listytop + object->findhigh[object->currentList]->type * (object->font[object->currentList]->get_yline() + object->getMarginBottom(object->currentList)); + const int32_t ybottom = ytop + object->font[object->currentList]->get_yline(); + + if (ybottom - object->scrollPos[object->currentList] > listybottom) + object->scrollPos[object->currentList] = ybottom - listybottom; + else if (ytop - object->scrollPos[object->currentList] < listytop) + object->scrollPos[object->currentList] = ytop - listytop; +} + +static void Menu_RunInput_FileSelect_Movement(MenuFileSelect_t *object, MenuMovement_t direction) +{ + switch (direction) + { + case MM_Up: + if (!object->findhigh[object->currentList]) + break; + if (object->findhigh[object->currentList]->prev) + { + object->findhigh[object->currentList] = object->findhigh[object->currentList]->prev; + break; + } + fallthrough__; + case MM_End: + object->findhigh[object->currentList] = object->findhigh[object->currentList]->userb; + break; + + case MM_Down: + if (!object->findhigh[object->currentList]) + break; + if (object->findhigh[object->currentList]->next) + { + object->findhigh[object->currentList] = object->findhigh[object->currentList]->next; + break; + } + fallthrough__; + case MM_Home: + object->findhigh[object->currentList] = object->findhigh[object->currentList]->usera; + break; + + case MM_Swap: + object->currentList = !object->currentList; + break; + + default: + break; + } + + Menu_RunInput_FileSelect_MovementVerify(object); +} + +static void Menu_RunInput_FileSelect_Select(MenuFileSelect_t *object) +{ + if (!object->findhigh[object->currentList]) + return; + + Bstrcat(object->destination, object->findhigh[object->currentList]->name); + + if (object->currentList == 0) + { + Bstrcat(object->destination, "/"); + Bcorrectfilename(object->destination, 1); + + Menu_FileSelectInit(object); + } + else + { + Menu_FileSelect(1); + } +} + +static void Menu_RunInput(Menu_t *cm) +{ + switch (cm->type) + { + case Panel: + { + MenuPanel_t *panel = (MenuPanel_t*)cm->object; + + if (I_ReturnTrigger() || Menu_RunInput_MouseReturn()) + { + I_ReturnTriggerClear(); + m_mousecaught = 1; + + S_PlaySound(EXITMENUSOUND); + + Menu_AnimateChange(cm->parentID, cm->parentAnimation); + } + else if (I_PanelUp()) + { + I_PanelUpClear(); + + S_PlaySound(KICK_HIT); + Menu_AnimateChange(panel->previousID, panel->previousAnimation); + } + else if (I_PanelDown() || Menu_RunInput_MouseAdvance()) + { + I_PanelDownClear(); + m_mousecaught = 1; + + S_PlaySound(KICK_HIT); + Menu_AnimateChange(panel->nextID, panel->nextAnimation); + } + break; + } + + case TextForm: + { + MenuTextForm_t *object = (MenuTextForm_t*)cm->object; + int32_t hitstate = I_EnterText(object->input, object->bufsize-1, 0); + + if (hitstate == -1 || Menu_RunInput_MouseReturn()) + { + m_mousecaught = 1; + + S_PlaySound(EXITMENUSOUND); + + object->input = NULL; + + Menu_AnimateChange(cm->parentID, cm->parentAnimation); + WithSDL2_StopTextInput(); + } + else if (hitstate == 1 || Menu_RunInput_MouseAdvance()) + { + m_mousecaught = 1; + + Menu_TextFormSubmit(object->input); + + object->input = NULL; + WithSDL2_StopTextInput(); + } + break; + } + + case FileSelect: + { + MenuFileSelect_t *object = (MenuFileSelect_t*)cm->object; + + if (I_ReturnTrigger() || Menu_RunInput_MouseReturn()) + { + I_ReturnTriggerClear(); + m_mousecaught = 1; + + S_PlaySound(EXITMENUSOUND); + + object->destination[0] = 0; + + Menu_FileSelect(0); + + Menu_AnimateChange(cm->parentID, MA_Return); + } + else if (I_AdvanceTrigger()) + { + I_AdvanceTriggerClear(); + + Menu_RunInput_FileSelect_Select(object); + + S_PlaySound(PISTOL_BODYHIT); + } + else if (KB_KeyPressed(sc_Home)) + { + KB_ClearKeyDown(sc_Home); + + Menu_RunInput_FileSelect_Movement(object, MM_Home); + + S_PlaySound(KICK_HIT); + } + else if (KB_KeyPressed(sc_End)) + { + KB_ClearKeyDown(sc_End); + + Menu_RunInput_FileSelect_Movement(object, MM_End); + + S_PlaySound(KICK_HIT); + } + else if (KB_KeyPressed(sc_PgUp)) + { + int32_t i; + + CACHE1D_FIND_REC *seeker = object->findhigh[object->currentList]; + + KB_ClearKeyDown(sc_PgUp); + + for (i = 0; i < 6; ++i) + { + if (seeker && seeker->prev) + seeker = seeker->prev; + } + + if (seeker) + { + object->findhigh[object->currentList] = seeker; + + Menu_RunInput_FileSelect_MovementVerify(object); + + S_PlaySound(KICK_HIT); + } + } + else if (KB_KeyPressed(sc_PgDn)) + { + int32_t i; + + CACHE1D_FIND_REC *seeker = object->findhigh[object->currentList]; + + KB_ClearKeyDown(sc_PgDn); + + for (i = 0; i < 6; ++i) + { + if (seeker && seeker->next) + seeker = seeker->next; + } + + if (seeker) + { + object->findhigh[object->currentList] = seeker; + + Menu_RunInput_FileSelect_MovementVerify(object); + + S_PlaySound(KICK_HIT); + } + } + else if (I_MenuLeft() || I_MenuRight()) + { + I_MenuLeftClear(); + I_MenuRightClear(); + + if ((object->currentList ? object->fnlist.numdirs : object->fnlist.numfiles) > 0) + { + Menu_RunInput_FileSelect_Movement(object, MM_Swap); + + S_PlaySound(KICK_HIT); + } + } + else if (I_MenuUp()) + { + I_MenuUpClear(); + + Menu_RunInput_FileSelect_Movement(object, MM_Up); + + S_PlaySound(KICK_HIT); + } + else if (I_MenuDown()) + { + I_MenuDownClear(); + + Menu_RunInput_FileSelect_Movement(object, MM_Down); + + S_PlaySound(KICK_HIT); + } + else + { + // JBF 20040208: seek to first name matching pressed character + char ch2, ch; + ch = KB_GetCh(); + if (ch > 0 && ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9'))) + { + CACHE1D_FIND_REC *seeker = object->findhigh[object->currentList]->usera; + if (ch >= 'a') + ch -= ('a'-'A'); + while (seeker) + { + ch2 = seeker->name[0]; + if (ch2 >= 'a' && ch2 <= 'z') + ch2 -= ('a'-'A'); + if (ch2 == ch) + break; + seeker = seeker->next; + } + if (seeker) + { + object->findhigh[object->currentList] = seeker; + + Menu_RunInput_FileSelect_MovementVerify(object); + + S_PlaySound(KICK_HIT); + } + } + } + + Menu_PreInput(NULL); + break; + } + + case Message: + if (I_ReturnTrigger() || Menu_RunInput_MouseReturn()) + { + I_ReturnTriggerClear(); + m_mousecaught = 1; + + S_PlaySound(EXITMENUSOUND); + + Menu_AnimateChange(cm->parentID, cm->parentAnimation); + } + + if (I_CheckAllInput()) + { + MenuMessage_t *message = (MenuMessage_t*)cm->object; + + I_ClearAllInput(); + + S_PlaySound(EXITMENUSOUND); + + Menu_AnimateChange(message->linkID, message->animation); + } + + Menu_PreInput(NULL); + break; + + case Verify: + if (I_ReturnTrigger() || KB_KeyPressed(sc_N) || Menu_RunInput_MouseReturn()) + { + I_ReturnTriggerClear(); + KB_ClearKeyDown(sc_N); + m_mousecaught = 1; + + Menu_Verify(0); + + Menu_AnimateChange(cm->parentID, cm->parentAnimation); + + S_PlaySound(EXITMENUSOUND); + } + + if (I_AdvanceTrigger() || KB_KeyPressed(sc_Y) || Menu_RunInput_MouseAdvance()) + { + MenuVerify_t *verify = (MenuVerify_t*)cm->object; + + I_AdvanceTriggerClear(); + KB_ClearKeyDown(sc_Y); + m_mousecaught = 1; + + Menu_Verify(1); + + Menu_AnimateChange(verify->linkID, verify->animation); + + S_PlaySound(PISTOL_BODYHIT); + } + + Menu_PreInput(NULL); + break; + + case Menu: + { + int32_t state; + + MenuMenu_t *menu = (MenuMenu_t*)cm->object; + MenuEntry_t *currentry = menu->entrylist[menu->currentEntry]; + + state = Menu_DetermineSpecialState(currentry); + + if (state == 0) + { + if (currentry != NULL) + switch (currentry->type) + { + case Dummy: + case Spacer: + break; + case Link: + if (currentry->flags & MEF_Disabled) + break; + if (I_AdvanceTrigger()) + { + I_AdvanceTriggerClear(); + + Menu_RunInput_EntryLink_Activate(currentry); + + if (g_player[myconnectindex].ps->gm&MODE_MENU) // for skill selection + S_PlaySound(PISTOL_BODYHIT); + } + break; + case Option: + { + MenuOption_t *object = (MenuOption_t*)currentry->entry; + + if (currentry->flags & MEF_Disabled) + break; + + if (I_AdvanceTrigger()) + { + I_AdvanceTriggerClear(); + + Menu_RunInput_EntryOption_Activate(currentry, object); + + S_PlaySound(PISTOL_BODYHIT); + } + else if (I_MenuRight()) + { + I_MenuRightClear(); + + Menu_RunInput_EntryOption_Movement(currentry, object, MM_Right); + + S_PlaySound(PISTOL_BODYHIT); + } + else if (I_MenuLeft()) + { + I_MenuLeftClear(); + + Menu_RunInput_EntryOption_Movement(currentry, object, MM_Left); + + S_PlaySound(PISTOL_BODYHIT); + } + } + break; + case Custom2Col: + if (I_MenuLeft() || I_MenuRight()) + { + I_MenuLeftClear(); + I_MenuRightClear(); + + Menu_RunInput_Menu_Movement(menu, MM_Swap); + + S_PlaySound(KICK_HIT); + } + + if (currentry->flags & MEF_Disabled) + break; + + if (I_AdvanceTrigger()) + { + I_AdvanceTriggerClear(); + + Menu_RunInput_EntryCustom2Col_Activate(currentry); + + S_PlaySound(PISTOL_BODYHIT); + } + break; + case RangeInt32: + { + MenuRangeInt32_t *object = (MenuRangeInt32_t*)currentry->entry; + + if (currentry->flags & MEF_Disabled) + break; + + if (I_SliderLeft()) + { + I_SliderLeftClear(); + + Menu_RunInput_EntryRangeInt32_Movement(currentry, object, MM_Left); + + S_PlaySound(KICK_HIT); + } + else if (I_SliderRight()) + { + I_SliderRightClear(); + + Menu_RunInput_EntryRangeInt32_Movement(currentry, object, MM_Right); + + S_PlaySound(KICK_HIT); + } + break; + } + case RangeFloat: + { + MenuRangeFloat_t *object = (MenuRangeFloat_t*)currentry->entry; + + if (currentry->flags & MEF_Disabled) + break; + + if (I_SliderLeft()) + { + I_SliderLeftClear(); + + Menu_RunInput_EntryRangeFloat_Movement(currentry, object, MM_Left); + + S_PlaySound(KICK_HIT); + } + else if (I_SliderRight()) + { + I_SliderRightClear(); + + Menu_RunInput_EntryRangeFloat_Movement(currentry, object, MM_Right); + + S_PlaySound(KICK_HIT); + } + break; + } +#ifdef MENU_ENABLE_RANGEDOUBLE + case RangeDouble: + { + MenuRangeDouble_t *object = (MenuRangeDouble_t*)currentry->entry; + + if (currentry->flags & MEF_Disabled) + break; + + if (I_SliderLeft()) + { + I_SliderLeftClear(); + + Menu_RunInput_EntryRangeDouble_Movement(/*currentry, */object, MM_Left); + + S_PlaySound(KICK_HIT); + } + else if (I_SliderRight()) + { + I_SliderRightClear(); + + Menu_RunInput_EntryRangeDouble_Movement(/*currentry, */object, MM_Right); + + S_PlaySound(KICK_HIT); + } + break; + } +#endif + + case String: + { + if (currentry->flags & MEF_Disabled) + break; + + if (I_AdvanceTrigger()) + { + I_AdvanceTriggerClear(); + + Menu_RunInput_EntryString_Activate(currentry); + + S_PlaySound(PISTOL_BODYHIT); + } + + break; + } + } + + if (I_ReturnTrigger() || I_EscapeTrigger() || Menu_RunInput_MouseReturn()) + { + I_ReturnTriggerClear(); + I_EscapeTriggerClear(); + m_mousecaught = 1; + + if (cm->parentID != MENU_CLOSE || (g_player[myconnectindex].ps->gm & MODE_GAME)) + S_PlaySound(EXITMENUSOUND); + + Menu_AnimateChange(cm->parentID, cm->parentAnimation); + } + else if (KB_KeyPressed(sc_Home)) + { + KB_ClearKeyDown(sc_Home); + + S_PlaySound(KICK_HIT); + + currentry = Menu_RunInput_Menu_Movement(menu, MM_Home); + } + else if (KB_KeyPressed(sc_End)) + { + KB_ClearKeyDown(sc_End); + + S_PlaySound(KICK_HIT); + + currentry = Menu_RunInput_Menu_Movement(menu, MM_End); + } + else if (I_MenuUp()) + { + I_MenuUpClear(); + + S_PlaySound(KICK_HIT); + + currentry = Menu_RunInput_Menu_Movement(menu, MM_Up); + } + else if (I_MenuDown()) + { + I_MenuDownClear(); + + S_PlaySound(KICK_HIT); + + currentry = Menu_RunInput_Menu_Movement(menu, MM_Down); + } + + if (currentry != NULL && !(currentry->flags & MEF_Disabled)) + Menu_PreInput(currentry); + } + else if (state == 1) + { + if (currentry->type == String) + { + MenuString_t *object = (MenuString_t*)currentry->entry; + + int32_t hitstate = I_EnterText(object->editfield, object->bufsize-1, object->flags); + + if (hitstate == -1 || Menu_RunInput_MouseReturn()) + { + m_mousecaught = 1; + + Menu_RunInput_EntryString_Cancel(/*currentry, */object); + + S_PlaySound(EXITMENUSOUND); + } + else if (hitstate == 1) + { + Menu_RunInput_EntryString_Submit(/*currentry, */object); + + S_PlaySound(PISTOL_BODYHIT); + } + } + } + else if (state == 2) + { + if (currentry->type == Option) + { + MenuOption_t *object = (MenuOption_t*)currentry->entry; + + if (I_ReturnTrigger() || Menu_RunInput_MouseReturn()) + { + I_ReturnTriggerClear(); + m_mousecaught = 1; + + S_PlaySound(EXITMENUSOUND); + + object->options->currentEntry = -1; + } + else if (I_AdvanceTrigger()) + { + I_AdvanceTriggerClear(); + + if (!Menu_RunInput_EntryOptionList_Activate(currentry, object)) + S_PlaySound(PISTOL_BODYHIT); + } + else if (KB_KeyPressed(sc_Home)) + { + KB_ClearKeyDown(sc_Home); + + S_PlaySound(KICK_HIT); + + Menu_RunInput_EntryOptionList_Movement(object, MM_Home); + } + else if (KB_KeyPressed(sc_End)) + { + KB_ClearKeyDown(sc_End); + + S_PlaySound(KICK_HIT); + + Menu_RunInput_EntryOptionList_Movement(object, MM_End); + } + else if (I_MenuUp()) + { + I_MenuUpClear(); + + S_PlaySound(KICK_HIT); + + Menu_RunInput_EntryOptionList_Movement(object, MM_Up); + } + else if (I_MenuDown()) + { + I_MenuDownClear(); + + S_PlaySound(KICK_HIT); + + Menu_RunInput_EntryOptionList_Movement(object, MM_Down); + } + } + else if (currentry->type == Custom2Col) + { + if (I_EscapeTrigger() || Menu_RunInput_MouseReturn()) + { + I_EscapeTriggerClear(); + m_mousecaught = 1; + + S_PlaySound(EXITMENUSOUND); + + ((MenuCustom2Col_t*)currentry->entry)->screenOpen = 0; + } + else if (Menu_PreCustom2ColScreen(currentry)) + ((MenuCustom2Col_t*)currentry->entry)->screenOpen = 0; + } + } + + break; + } + } +} + +// This function MUST NOT RECURSE. That is why Menu_Run is separate. +void M_DisplayMenus(void) +{ + vec2_t origin = { 0, 0 }, previousOrigin = { 0, 0 }; + + Net_GetPackets(); + + if ((g_player[myconnectindex].ps->gm&MODE_MENU) == 0) + { + walock[TILE_LOADSHOT] = 1; + return; + } + + if (!Menu_IsTextInput(m_currentMenu) && KB_KeyPressed(sc_Q)) + Menu_AnimateChange(MENU_QUIT, MA_Advance); + + int32_t mousestatus = readmouseabsxy(&m_mousepos, &mouseabs); + if (mousestatus && mousepressstate == Mouse_Pressed) + m_mousedownpos = m_mousepos; + + Menu_RunInput(m_currentMenu); + + g_player[myconnectindex].ps->gm &= (0xff-MODE_TYPE); + // g_player[myconnectindex].ps->fta = 0; + + int32_t const backgroundOK = Menu_BlackTranslucentBackgroundOK(g_currentMenu); + + // need EVENT_DISPLAYMENUBACKGROUND here + + if (!KXDWN && ((g_player[myconnectindex].ps->gm&MODE_GAME) || ud.recstat==2) && backgroundOK) + fade_screen_black(1); + + if (Menu_UpdateScreenOK(g_currentMenu)) + G_UpdateScreenArea(); + +#if !defined EDUKE32_TOUCH_DEVICES + if (m_menuchange_watchpoint > 0) + m_menuchange_watchpoint++; +#endif + + if (m_parentMenu) + { + ud.m_origin = origin; + VM_OnEventWithReturn(EVENT_DISPLAYINACTIVEMENU, g_player[screenpeek].ps->i, screenpeek, m_parentMenu->menuID); + origin = ud.m_origin; + } + + // Determine animation values. + if (totalclock < m_animation.start + m_animation.length) + { + const int32_t screenwidth = scale(240<<16, xdim, ydim); + + origin.x = mulscale15(screenwidth, m_animation.in(&m_animation)); + previousOrigin.x = mulscale15(screenwidth, m_animation.out(&m_animation)); + + ud.m_origin = previousOrigin; + VM_OnEventWithReturn(EVENT_DISPLAYINACTIVEMENU, g_player[screenpeek].ps->i, screenpeek, m_animation.previous->menuID); + previousOrigin = ud.m_origin; + } + + ud.m_origin = origin; + VM_OnEventWithReturn(EVENT_DISPLAYMENU, g_player[screenpeek].ps->i, screenpeek, g_currentMenu); + origin = ud.m_origin; + + if (m_parentMenu && backgroundOK) + { + Menu_Run(m_parentMenu, origin); + } + + // hack; need EVENT_DISPLAYMENUBACKGROUND above + if (KXDWN && ((g_player[myconnectindex].ps->gm&MODE_GAME) || ud.recstat==2 || m_parentMenu != NULL) && backgroundOK) + fade_screen_black(1); + + // Display the menu, with a transition animation if applicable. + if (totalclock < m_animation.start + m_animation.length) + { + Menu_Run(m_animation.previous, previousOrigin); + Menu_Run(m_animation.current, origin); + } + else + Menu_Run(m_currentMenu, origin); + +#if !defined EDUKE32_TOUCH_DEVICES + if (m_menuchange_watchpoint >= 3) + m_menuchange_watchpoint = 0; +#endif + + if (m_parentMenu) + { + ud.m_origin = origin; + VM_OnEventWithReturn(EVENT_DISPLAYINACTIVEMENUREST, g_player[screenpeek].ps->i, screenpeek, m_parentMenu->menuID); + } + + if (totalclock < m_animation.start + m_animation.length) + { + ud.m_origin = previousOrigin; + VM_OnEventWithReturn(EVENT_DISPLAYINACTIVEMENUREST, g_player[screenpeek].ps->i, screenpeek, m_animation.previous->menuID); + } + + ud.m_origin = origin; + VM_OnEventWithReturn(EVENT_DISPLAYMENUREST, g_player[screenpeek].ps->i, screenpeek, g_currentMenu); + +#if !defined EDUKE32_TOUCH_DEVICES + if (tilesiz[CROSSHAIR].x > 0 && mousestatus) +#else + if (mousestatus) +#endif + { +#if !defined EDUKE32_TOUCH_DEVICES + if (!MOUSEACTIVECONDITION) + m_mousewake_watchpoint = 1; +#endif + + if (MOUSEACTIVECONDITIONAL(mousepressstateadvance()) || m_mousepos.x != m_prevmousepos.x || m_mousepos.y != m_prevmousepos.y) + { + m_prevmousepos = m_mousepos; + m_mouselastactivity = totalclock; + } +#if !defined EDUKE32_TOUCH_DEVICES + else + m_mousewake_watchpoint = 0; +#endif + + m_mousecaught = 0; + } + else + { + m_mouselastactivity = -M_MOUSETIMEOUT; + +#if !defined EDUKE32_TOUCH_DEVICES + m_mousewake_watchpoint = 0; +#endif + } + +#ifndef EDUKE32_TOUCH_DEVICES + // Display the mouse cursor, except on touch devices. + if (MOUSEACTIVECONDITION) + { + int32_t a = VM_OnEventWithReturn(EVENT_DISPLAYCURSOR, g_player[screenpeek].ps->i, screenpeek, CROSSHAIR); + + if ((unsigned) a < MAXTILES) + rotatesprite_fs_alpha(m_mousepos.x, m_mousepos.y, 65536, 0, a, 0, CROSSHAIR_PAL, 2|1, MOUSEALPHA); + } + else + mousepressstate = Mouse_Idle; +#endif + + if ((g_player[myconnectindex].ps->gm&MODE_MENU) != MODE_MENU) + { + G_UpdateScreenArea(); + CAMERACLOCK = totalclock; + CAMERADIST = 65536; + } +} diff --git a/patch/osdcmds.cpp b/patch/osdcmds.cpp new file mode 100644 index 0000000..34cde37 --- /dev/null +++ b/patch/osdcmds.cpp @@ -0,0 +1,1773 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2010 EDuke32 developers and contributors + +This file is part of EDuke32. + +EDuke32 is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- + +#include "duke3d.h" +#include "osdcmds.h" +#include "menus.h" +#include "osdfuncs.h" +#include "demo.h" // g_firstDemoFile[] +#include "cheats.h" +#include "sbar.h" + +#ifdef LUNATIC +# include "lunatic_game.h" +#endif + +#ifdef EDUKE32_TOUCH_DEVICES +#include "in_android.h" +#endif + +struct osdcmd_cheatsinfo osdcmd_cheatsinfo_stat; +float r_ambientlight = 1.0, r_ambientlightrecip = 1.0; + +uint32_t cl_cheatmask; + +static inline int32_t osdcmd_quit(osdfuncparm_t const * const UNUSED(parm)) +{ + UNREFERENCED_CONST_PARAMETER(parm); + OSD_ShowDisplay(0); + G_GameQuit(); + return OSDCMD_OK; +} + +static int32_t osdcmd_changelevel(osdfuncparm_t const * const parm) +{ + int32_t volume=0,level; + char *p; + + if (!VOLUMEONE) + { + if (parm->numparms != 2) return OSDCMD_SHOWHELP; + + volume = strtol(parm->parms[0], &p, 10) - 1; + if (p[0]) return OSDCMD_SHOWHELP; + level = strtol(parm->parms[1], &p, 10) - 1; + if (p[0]) return OSDCMD_SHOWHELP; + } + else + { + if (parm->numparms != 1) return OSDCMD_SHOWHELP; + + level = strtol(parm->parms[0], &p, 10) - 1; + if (p[0]) return OSDCMD_SHOWHELP; + } + + if (volume < 0) return OSDCMD_SHOWHELP; + if (level < 0) return OSDCMD_SHOWHELP; + + if (!VOLUMEONE) + { + if (volume > g_volumeCnt) + { + OSD_Printf("changelevel: invalid volume number (range 1-%d)\n",g_volumeCnt); + return OSDCMD_OK; + } + } + + if (level > MAXLEVELS || g_mapInfo[volume *MAXLEVELS+level].filename == NULL) + { + OSD_Printf("changelevel: invalid level number\n"); + return OSDCMD_SHOWHELP; + } + + if (numplayers > 1) + { + /* + if (g_netServer) + Net_NewGame(volume,level); + else if (voting == -1) + { + ud.m_volume_number = volume; + ud.m_level_number = level; + + if (g_player[myconnectindex].ps->i) + { + int32_t i; + + for (i=0; i<MAXPLAYERS; i++) + { + g_player[i].vote = 0; + g_player[i].gotvote = 0; + } + + g_player[myconnectindex].vote = g_player[myconnectindex].gotvote = 1; + + voting = myconnectindex; + + tempbuf[0] = PACKET_MAP_VOTE_INITIATE; + tempbuf[1] = myconnectindex; + tempbuf[2] = ud.m_volume_number; + tempbuf[3] = ud.m_level_number; + + enet_peer_send(g_netClientPeer, CHAN_GAMESTATE, enet_packet_create(tempbuf, 4, ENET_PACKET_FLAG_RELIABLE)); + } + if ((g_gametypeFlags[ud.m_coop] & GAMETYPE_PLAYERSFRIENDLY) && !(g_gametypeFlags[ud.m_coop] & GAMETYPE_TDM)) + ud.m_noexits = 0; + + M_OpenMenu(myconnectindex); + Menu_Change(MENU_NETWAITVOTES); + } + */ + return OSDCMD_OK; + } + if (g_player[myconnectindex].ps->gm & MODE_GAME) + { + // in-game behave like a cheat + osdcmd_cheatsinfo_stat.cheatnum = CHEAT_SCOTTY; + osdcmd_cheatsinfo_stat.volume = volume; + osdcmd_cheatsinfo_stat.level = level; + } + else + { + // out-of-game behave like a menu command + osdcmd_cheatsinfo_stat.cheatnum = -1; + + ud.m_volume_number = volume; + ud.m_level_number = level; + + ud.m_monsters_off = 0; + ud.monsters_off = 0; + + ud.m_respawn_items = 0; + ud.m_respawn_inventory = 0; + + ud.multimode = 1; + + G_NewGame_EnterLevel(); + } + + return OSDCMD_OK; +} + +static int32_t osdcmd_map(osdfuncparm_t const * const parm) +{ + int32_t i; + char filename[BMAX_PATH]; + + const int32_t wildcardp = parm->numparms==1 && + (Bstrchr(parm->parms[0], '*') != NULL); + + if (parm->numparms != 1 || wildcardp) + { + CACHE1D_FIND_REC *r; + fnlist_t fnlist = FNLIST_INITIALIZER; + int32_t maxwidth = 0; + + if (wildcardp) + maybe_append_ext(filename, sizeof(filename), parm->parms[0], ".map"); + else + Bstrcpy(filename, "*.MAP"); + + fnlist_getnames(&fnlist, "/", filename, -1, 0); + + for (r=fnlist.findfiles; r; r=r->next) + maxwidth = max((unsigned)maxwidth, Bstrlen(r->name)); + + if (maxwidth > 0) + { + int32_t x = 0, count = 0; + maxwidth += 3; + OSD_Printf(OSDTEXT_RED "Map listing:\n"); + for (r=fnlist.findfiles; r; r=r->next) + { + OSD_Printf("%-*s",maxwidth,r->name); + x += maxwidth; + count++; + if (x > OSD_GetCols() - maxwidth) + { + x = 0; + OSD_Printf("\n"); + } + } + if (x) OSD_Printf("\n"); + OSD_Printf(OSDTEXT_RED "Found %d maps\n", fnlist.numfiles); + } + + fnlist_clearnames(&fnlist); + + return OSDCMD_SHOWHELP; + } + + maybe_append_ext(filename, sizeof(filename), parm->parms[0], ".map"); + + if ((i = kopen4loadfrommod(filename,0)) < 0) + { + OSD_Printf(OSD_ERROR "map: file \"%s\" not found.\n", filename); + return OSDCMD_OK; + } + kclose(i); + + boardfilename[0] = '/'; + boardfilename[1] = 0; + strcat(boardfilename, filename); + + if (numplayers > 1) + { + /* + if (g_netServer) + { + Net_SendUserMapName(); + ud.m_volume_number = 0; + ud.m_level_number = 7; + Net_NewGame(ud.m_volume_number, ud.m_level_number); + } + else if (voting == -1) + { + Net_SendUserMapName(); + + ud.m_volume_number = 0; + ud.m_level_number = 7; + + if (g_player[myconnectindex].ps->i) + { + int32_t i; + + for (i=0; i<MAXPLAYERS; i++) + { + g_player[i].vote = 0; + g_player[i].gotvote = 0; + } + + g_player[myconnectindex].vote = g_player[myconnectindex].gotvote = 1; + voting = myconnectindex; + + tempbuf[0] = PACKET_MAP_VOTE_INITIATE; + tempbuf[1] = myconnectindex; + tempbuf[2] = ud.m_volume_number; + tempbuf[3] = ud.m_level_number; + + enet_peer_send(g_netClientPeer, CHAN_GAMESTATE, enet_packet_create(tempbuf, 4, ENET_PACKET_FLAG_RELIABLE)); + } + if ((g_gametypeFlags[ud.m_coop] & GAMETYPE_PLAYERSFRIENDLY) && !(g_gametypeFlags[ud.m_coop] & GAMETYPE_TDM)) + ud.m_noexits = 0; + + M_OpenMenu(myconnectindex); + Menu_Change(MENU_NETWAITVOTES); + } + */ + return OSDCMD_OK; + } + + osdcmd_cheatsinfo_stat.cheatnum = -1; + ud.m_volume_number = 0; + ud.m_level_number = 7; + + ud.m_monsters_off = ud.monsters_off = 0; + + ud.m_respawn_items = 0; + ud.m_respawn_inventory = 0; + + ud.multimode = 1; + + G_NewGame_EnterLevel(); + + return OSDCMD_OK; +} + +// demo <demonum or demofn> [<prof>] +// +// To profile a demo ("timedemo mode"), <prof> can be given in the range 0-8, +// which will start to replay it as fast as possible, rendering <prof> frames +// for each gametic. +// +// Notes: +// * The demos should be recorded with demorec_diffs set to 0, so that the +// game state updates are actually computed. +// * Currently, the profiling can only be aborted on SDL 1.2 builds by +// pressing any key. +// * With <prof> greater than 1, interpolation should be calculated properly, +// though this has not been verified by looking at the frames. +// * When testing whether a change in the source has an effect on performance, +// the variance of the run times MUST be taken into account (that is, the +// replaying must be performed multiple times for the old and new versions, +// etc.) +static int32_t osdcmd_demo(osdfuncparm_t const * const parm) +{ + if (numplayers > 1) + { + OSD_Printf("Command not allowed in multiplayer\n"); + return OSDCMD_OK; + } + + if (g_player[myconnectindex].ps->gm & MODE_GAME) + { + OSD_Printf("demo: Must not be in a game.\n"); + return OSDCMD_OK; + } + + if (parm->numparms != 1 && parm->numparms != 2) + return OSDCMD_SHOWHELP; + + { + int32_t prof = parm->numparms==2 ? Batoi(parm->parms[1]) : -1; + + Demo_SetFirst(parm->parms[0]); + Demo_PlayFirst(clamp(prof, -1, 8)+1, 0); + } + + return OSDCMD_OK; +} + +static int32_t osdcmd_activatecheat(osdfuncparm_t const * const parm) +{ + if (parm->numparms != 1) + return OSDCMD_SHOWHELP; + + if (numplayers == 1 && g_player[myconnectindex].ps->gm & MODE_GAME) + osdcmd_cheatsinfo_stat.cheatnum = Batoi(parm->parms[0]); + else + OSD_Printf("activatecheat: Not in a single-player game.\n"); + + return OSDCMD_OK; +} + +static int32_t osdcmd_god(osdfuncparm_t const * const UNUSED(parm)) +{ + UNREFERENCED_CONST_PARAMETER(parm); + if (numplayers == 1 && g_player[myconnectindex].ps->gm & MODE_GAME) + osdcmd_cheatsinfo_stat.cheatnum = CHEAT_CORNHOLIO; + else + OSD_Printf("god: Not in a single-player game.\n"); + + return OSDCMD_OK; +} + +static int32_t osdcmd_noclip(osdfuncparm_t const * const UNUSED(parm)) +{ + UNREFERENCED_CONST_PARAMETER(parm); + + if (numplayers == 1 && g_player[myconnectindex].ps->gm & MODE_GAME) + { + osdcmd_cheatsinfo_stat.cheatnum = CHEAT_CLIP; + } + else + { + OSD_Printf("noclip: Not in a single-player game.\n"); + } + + return OSDCMD_OK; +} + +static int32_t osdcmd_restartsound(osdfuncparm_t const * const UNUSED(parm)) +{ + UNREFERENCED_CONST_PARAMETER(parm); + S_SoundShutdown(); + S_MusicShutdown(); + + S_MusicStartup(); + S_SoundStartup(); + + FX_StopAllSounds(); + S_ClearSoundLocks(); + + if (ud.config.MusicToggle) + S_RestartMusic(); + + return OSDCMD_OK; +} + +static int32_t osdcmd_music(osdfuncparm_t const * const parm) +{ + if (parm->numparms == 1) + { + int32_t sel = G_GetMusicIdx(parm->parms[0]); + + if (sel == -1) + return OSDCMD_SHOWHELP; + + if (sel == -2) + { + OSD_Printf("%s is not a valid episode/level number pair\n", parm->parms[0]); + return OSDCMD_OK; + } + + if (g_mapInfo[sel].musicfn != NULL) + { + g_musicIndex = sel; + G_StartMusic(); + } + else + { + OSD_Printf("No music defined for %s\n", parm->parms[0]); + } + + return OSDCMD_OK; + } + + return OSDCMD_SHOWHELP; +} + +int32_t osdcmd_restartvid(osdfuncparm_t const * const UNUSED(parm)) +{ + UNREFERENCED_CONST_PARAMETER(parm); + resetvideomode(); + if (setgamemode(ud.config.ScreenMode,ud.config.ScreenWidth,ud.config.ScreenHeight,ud.config.ScreenBPP)) + G_GameExit("restartvid: Reset failed...\n"); + onvideomodechange(ud.config.ScreenBPP>8); + G_UpdateScreenArea(); + + return OSDCMD_OK; +} + +int32_t osdcmd_restartmap(osdfuncparm_t const * const UNUSED(parm)) +{ + UNREFERENCED_CONST_PARAMETER(parm); + + if (g_player[myconnectindex].ps->gm & MODE_GAME && ud.multimode == 1) + g_player[myconnectindex].ps->gm = MODE_RESTART; + + return OSDCMD_OK; +} + +static int32_t osdcmd_vidmode(osdfuncparm_t const * const parm) +{ + int32_t newbpp = ud.config.ScreenBPP, newwidth = ud.config.ScreenWidth, + newheight = ud.config.ScreenHeight, newfs = ud.config.ScreenMode; + int32_t tmp; + + if (parm->numparms < 1 || parm->numparms > 4) return OSDCMD_SHOWHELP; + + switch (parm->numparms) + { + case 1: // bpp switch + tmp = Batol(parm->parms[0]); + if (!(tmp==8 || tmp==16 || tmp==32)) + return OSDCMD_SHOWHELP; + newbpp = tmp; + break; + case 2: // res switch + newwidth = Batol(parm->parms[0]); + newheight = Batol(parm->parms[1]); + break; + case 3: // res & bpp switch + case 4: + newwidth = Batol(parm->parms[0]); + newheight = Batol(parm->parms[1]); + tmp = Batol(parm->parms[2]); + if (!(tmp==8 || tmp==16 || tmp==32)) + return OSDCMD_SHOWHELP; + newbpp = tmp; + if (parm->numparms == 4) + newfs = (Batol(parm->parms[3]) != 0); + break; + } + + if (setgamemode(newfs,newwidth,newheight,newbpp)) + { + initprintf("vidmode: Mode change failed!\n"); + if (setgamemode(ud.config.ScreenMode, ud.config.ScreenWidth, ud.config.ScreenHeight, ud.config.ScreenBPP)) + G_GameExit("vidmode: Reset failed!\n"); + } + ud.config.ScreenBPP = newbpp; + ud.config.ScreenWidth = newwidth; + ud.config.ScreenHeight = newheight; + ud.config.ScreenMode = newfs; + onvideomodechange(ud.config.ScreenBPP>8); + G_UpdateScreenArea(); + return OSDCMD_OK; +} + +#ifdef LUNATIC +// Returns: INT32_MIN if no such CON label, its value else. +LUNATIC_CB int32_t (*El_GetLabelValue)(const char *label); +#endif + +static int32_t osdcmd_spawn(osdfuncparm_t const * const parm) +{ + int32_t picnum = 0; + uint16_t cstat=0; + char pal=0; + int16_t ang=0; + int16_t set=0, idx; + vec3_t vect; + + if (numplayers > 1 || !(g_player[myconnectindex].ps->gm & MODE_GAME)) + { + OSD_Printf("spawn: Can't spawn sprites in multiplayer games or demos\n"); + return OSDCMD_OK; + } + + switch (parm->numparms) + { + case 7: // x,y,z + vect.x = Batol(parm->parms[4]); + vect.y = Batol(parm->parms[5]); + vect.z = Batol(parm->parms[6]); + set |= 8; + fallthrough__; + case 4: // ang + ang = Batol(parm->parms[3]) & 2047; + set |= 4; + fallthrough__; + case 3: // cstat + cstat = (uint16_t)Batol(parm->parms[2]); + set |= 2; + fallthrough__; + case 2: // pal + pal = (uint8_t)Batol(parm->parms[1]); + set |= 1; + fallthrough__; + case 1: // tile number + if (isdigit(parm->parms[0][0])) + { + picnum = Batol(parm->parms[0]); + } + else + { + int32_t i; +#ifdef LUNATIC + i = g_labelCnt; + picnum = El_GetLabelValue(parm->parms[0]); + if (picnum != INT32_MIN) + i = !i; +#else + int32_t j; + + for (j=0; j<2; j++) + { + for (i=0; i<g_labelCnt; i++) + { + if ((j == 0 && !Bstrcmp(label+(i<<6), parm->parms[0])) || + (j == 1 && !Bstrcasecmp(label+(i<<6), parm->parms[0]))) + { + picnum = labelcode[i]; + break; + } + } + + if (i < g_labelCnt) + break; + } +#endif + if (i==g_labelCnt) + { + OSD_Printf("spawn: Invalid tile label given\n"); + return OSDCMD_OK; + } + } + + if ((uint32_t)picnum >= MAXUSERTILES) + { + OSD_Printf("spawn: Invalid tile number\n"); + return OSDCMD_OK; + } + break; + + default: + return OSDCMD_SHOWHELP; + } + + idx = A_Spawn(g_player[myconnectindex].ps->i, picnum); + if (set & 1) sprite[idx].pal = (uint8_t)pal; + if (set & 2) sprite[idx].cstat = (int16_t)cstat; + if (set & 4) sprite[idx].ang = ang; + if (set & 8) + { + if (setsprite(idx, &vect) < 0) + { + OSD_Printf("spawn: Sprite can't be spawned into null space\n"); + A_DeleteSprite(idx); + } + } + + return OSDCMD_OK; +} + +#if !defined LUNATIC +static int32_t osdcmd_setvar(osdfuncparm_t const * const parm) +{ + int32_t i, varval; + char varname[256]; + + if (parm->numparms != 2) return OSDCMD_SHOWHELP; + + if (numplayers > 1) + { + OSD_Printf("Command not allowed in multiplayer\n"); + return OSDCMD_OK; + } + + strcpy(varname,parm->parms[1]); + varval = Batol(varname); + i = hash_find(&h_gamevars,varname); + if (i >= 0) + varval=Gv_GetVar(i, g_player[screenpeek].ps->i, screenpeek); + + strcpy(varname,parm->parms[0]); + i = hash_find(&h_gamevars,varname); + if (i >= 0) + Gv_SetVar(i, varval, g_player[screenpeek].ps->i, screenpeek); + return OSDCMD_OK; +} + +static int32_t osdcmd_addlogvar(osdfuncparm_t const * const parm) +{ + int32_t i; + char varname[256]; + + if (parm->numparms != 1) return OSDCMD_SHOWHELP; + + if (numplayers > 1) + { + OSD_Printf("Command not allowed in multiplayer\n"); + return OSDCMD_OK; + } + + strcpy(varname,parm->parms[0]); + i = hash_find(&h_gamevars,varname); + if (i >= 0) + OSD_Printf("%s = %d\n", varname, Gv_GetVar(i, g_player[screenpeek].ps->i, screenpeek)); + return OSDCMD_OK; +} + +static int32_t osdcmd_setactorvar(osdfuncparm_t const * const parm) +{ + int32_t i, varval, ID; + char varname[256]; + + if (parm->numparms != 3) return OSDCMD_SHOWHELP; + + if (numplayers > 1) + { + OSD_Printf("Command not allowed in multiplayer\n"); + return OSDCMD_OK; + } + + ID=Batol(parm->parms[0]); + if (ID>=MAXSPRITES) + { + OSD_Printf("Invalid sprite ID\n"); + return OSDCMD_OK; + } + + varval = Batol(parm->parms[2]); + strcpy(varname,parm->parms[2]); + varval = Batol(varname); + i = hash_find(&h_gamevars,varname); + if (i >= 0) + varval=Gv_GetVar(i, g_player[screenpeek].ps->i, screenpeek); + + strcpy(varname,parm->parms[1]); + i = hash_find(&h_gamevars,varname); + if (i >= 0) + Gv_SetVar(i, varval, ID, -1); + return OSDCMD_OK; +} +#else +static int32_t osdcmd_lua(osdfuncparm_t const * const parm) +{ + // Should be used like + // lua "lua code..." + // (the quotes making the whole string passed as one argument) + + int32_t ret; + + if (parm->numparms != 1) + return OSDCMD_SHOWHELP; + + if (!L_IsInitialized(&g_ElState)) + { + OSD_Printf("Lua state is not initialized.\n"); + return OSDCMD_OK; + } + + // TODO: "=<expr>" as shorthand for "print(<expr>)", like in the + // stand-alone Lua interpreter? + // TODO: reserve some table to explicitly store stuff on the top level, for + // debugging convenience? + + // For the 'lua' OSD command, don't make errors appear on-screen: + el_addNewErrors = 0; + ret = L_RunString(&g_ElState, parm->parms[0], -1, "console"); + el_addNewErrors = 1; + + if (ret != 0) + OSD_Printf("Error running the Lua code (error code %d)\n", ret); + + return OSDCMD_OK; +} +#endif + +static int32_t osdcmd_addpath(osdfuncparm_t const * const parm) +{ + char pathname[BMAX_PATH]; + + if (parm->numparms != 1) return OSDCMD_SHOWHELP; + + strcpy(pathname,parm->parms[0]); + addsearchpath(pathname); + return OSDCMD_OK; +} + +static int32_t osdcmd_initgroupfile(osdfuncparm_t const * const parm) +{ + char file[BMAX_PATH]; + + if (parm->numparms != 1) return OSDCMD_SHOWHELP; + + strcpy(file,parm->parms[0]); + initgroupfile(file); + return OSDCMD_OK; +} + +static int32_t osdcmd_cmenu(osdfuncparm_t const * const parm) +{ + if (parm->numparms != 1) return OSDCMD_SHOWHELP; + if (numplayers > 1) + { + OSD_Printf("cmenu: disallowed in multiplayer\n"); + return OSDCMD_OK; + } + else + { + Menu_Change(Batol(parm->parms[0])); + } + + return OSDCMD_OK; +} + +extern void G_SetCrosshairColor(int32_t r, int32_t g, int32_t b); +extern palette_t CrosshairColors; + +static int32_t osdcmd_crosshaircolor(osdfuncparm_t const * const parm) +{ + int32_t r, g, b; + + if (parm->numparms != 3) + { + OSD_Printf("crosshaircolor: r:%d g:%d b:%d\n",CrosshairColors.r,CrosshairColors.g,CrosshairColors.b); + return OSDCMD_SHOWHELP; + } + r = Batol(parm->parms[0]); + g = Batol(parm->parms[1]); + b = Batol(parm->parms[2]); + G_SetCrosshairColor(r,g,b); + + if (!OSD_ParsingScript()) + OSD_Printf("%s\n", parm->raw); + + return OSDCMD_OK; +} + +static int32_t osdcmd_give(osdfuncparm_t const * const parm) +{ + int32_t i; + + if (numplayers != 1 || (g_player[myconnectindex].ps->gm & MODE_GAME) == 0 || + g_player[myconnectindex].ps->dead_flag != 0) + { + OSD_Printf("give: Cannot give while dead or not in a single-player game.\n"); + return OSDCMD_OK; + } + + if (parm->numparms != 1) return OSDCMD_SHOWHELP; + + if (!Bstrcasecmp(parm->parms[0], "all")) + { + osdcmd_cheatsinfo_stat.cheatnum = CHEAT_STUFF; + return OSDCMD_OK; + } + else if (!Bstrcasecmp(parm->parms[0], "health")) + { + sprite[g_player[myconnectindex].ps->i].extra = g_player[myconnectindex].ps->max_player_health<<1; + return OSDCMD_OK; + } + else if (!Bstrcasecmp(parm->parms[0], "weapons")) + { + osdcmd_cheatsinfo_stat.cheatnum = CHEAT_WEAPONS; + return OSDCMD_OK; + } + else if (!Bstrcasecmp(parm->parms[0], "ammo")) + { + for (i=MAX_WEAPONS-(VOLUMEONE?6:1)-1; i>=PISTOL_WEAPON; i--) + P_AddAmmo(g_player[myconnectindex].ps,i,g_player[myconnectindex].ps->max_ammo_amount[i]); + return OSDCMD_OK; + } + else if (!Bstrcasecmp(parm->parms[0], "armor")) + { + g_player[myconnectindex].ps->inv_amount[GET_SHIELD] = 100; + return OSDCMD_OK; + } + else if (!Bstrcasecmp(parm->parms[0], "keys")) + { + osdcmd_cheatsinfo_stat.cheatnum = CHEAT_KEYS; + return OSDCMD_OK; + } + else if (!Bstrcasecmp(parm->parms[0], "inventory")) + { + osdcmd_cheatsinfo_stat.cheatnum = CHEAT_INVENTORY; + return OSDCMD_OK; + } + return OSDCMD_SHOWHELP; +} + +void onvideomodechange(int32_t newmode) +{ + uint8_t palid; + + // XXX? + if (!newmode || g_player[screenpeek].ps->palette < BASEPALCOUNT) + palid = g_player[screenpeek].ps->palette; + else + palid = BASEPAL; + +#ifdef POLYMER + if (getrendermode() == REND_POLYMER) + { + int32_t i = 0; + + while (i < MAXSPRITES) + { + if (actor[i].lightptr) + { + polymer_deletelight(actor[i].lightId); + actor[i].lightptr = NULL; + actor[i].lightId = -1; + } + i++; + } + } +#endif + + setbrightness(ud.brightness>>2, palid, 0); + g_restorePalette = -1; + g_crosshairSum = -1; +} + +static int32_t osdcmd_name(osdfuncparm_t const * const parm) +{ + char namebuf[32]; + + if (parm->numparms != 1) + { + OSD_Printf("\"name\" is \"%s\"\n",szPlayerName); + return OSDCMD_SHOWHELP; + } + + Bstrcpy(tempbuf,parm->parms[0]); + + while (Bstrlen(OSD_StripColors(namebuf,tempbuf)) > 10) + tempbuf[Bstrlen(tempbuf)-1] = '\0'; + + Bstrncpy(szPlayerName,tempbuf,sizeof(szPlayerName)-1); + szPlayerName[sizeof(szPlayerName)-1] = '\0'; + + OSD_Printf("name %s\n",szPlayerName); + + Net_SendClientInfo(); + + return OSDCMD_OK; +} + + +static int32_t osdcmd_button(osdfuncparm_t const * const parm) +{ + char const *p = parm->name+9; // skip "gamefunc_" +// if (g_player[myconnectindex].ps->gm == MODE_GAME) // only trigger these if in game + CONTROL_OSDInput[CONFIG_FunctionNameToNum(p)] = 1; // FIXME + return OSDCMD_OK; +} + +const keydef_t ConsoleKeys[]= +{ + { "Escape", 0x1 }, + { "1", 0x2 }, + { "2", 0x3 }, + { "3", 0x4 }, + { "4", 0x5 }, + { "5", 0x6 }, + { "6", 0x7 }, + { "7", 0x8 }, + { "8", 0x9 }, + { "9", 0xa }, + { "0", 0xb }, + { "-", 0xc }, + { "=", 0xd }, + { "BakSpc", 0xe }, + { "Tab", 0xf }, + { "Q", 0x10 }, + { "W", 0x11 }, + { "E", 0x12 }, + { "R", 0x13 }, + { "T", 0x14 }, + { "Y", 0x15 }, + { "U", 0x16 }, + { "I", 0x17 }, + { "O", 0x18 }, + { "P", 0x19 }, + { "[", 0x1a }, + { "]", 0x1b }, + { "Enter", 0x1c }, + { "LCtrl", 0x1d }, + { "A", 0x1e }, + { "S", 0x1f }, + { "D", 0x20 }, + { "F", 0x21 }, + { "G", 0x22 }, + { "H", 0x23 }, + { "J", 0x24 }, + { "K", 0x25 }, + { "L", 0x26 }, + { "SemiColon", 0x27 }, + { "'", 0x28 }, + { "Tilde", 0x29 }, + { "LShift", 0x2a }, + { "Backslash", 0x2b }, + { "Z", 0x2c }, + { "X", 0x2d }, + { "C", 0x2e }, + { "V", 0x2f }, + { "B", 0x30 }, + { "N", 0x31 }, + { "M", 0x32 }, + { ",", 0x33 }, + { ".", 0x34 }, + { "/", 0x35 }, + { "RShift", 0x36 }, + { "Kpad*", 0x37 }, + { "LAlt", 0x38 }, + { "Space", 0x39 }, + { "CapLck", 0x3a }, + { "F1", 0x3b }, + { "F2", 0x3c }, + { "F3", 0x3d }, + { "F4", 0x3e }, + { "F5", 0x3f }, + { "F6", 0x40 }, + { "F7", 0x41 }, + { "F8", 0x42 }, + { "F9", 0x43 }, + { "F10", 0x44 }, + { "NumLck", 0x45 }, + { "ScrLck", 0x46 }, + { "Kpad7", 0x47 }, + { "Kpad8", 0x48 }, + { "Kpad9", 0x49 }, + { "Kpad-", 0x4a }, + { "Kpad4", 0x4b }, + { "Kpad5", 0x4c }, + { "Kpad6", 0x4d }, + { "Kpad+", 0x4e }, + { "Kpad1", 0x4f }, + { "Kpad2", 0x50 }, + { "Kpad3", 0x51 }, + { "Kpad0", 0x52 }, + { "Kpad.", 0x53 }, + { "F11", 0x57 }, + { "F12", 0x58 }, + { "KpdEnt", 0x9c }, + { "RCtrl", 0x9d }, + { "Kpad/", 0xb5 }, + { "RAlt", 0xb8 }, + { "PrtScn", 0xb7 }, + { "Pause", 0xc5 }, + { "Home", 0xc7 }, + { "Up", 0xc8 }, + { "PgUp", 0xc9 }, + { "Left", 0xcb }, + { "Right", 0xcd }, + { "End", 0xcf }, + { "Down", 0xd0 }, + { "PgDn", 0xd1 }, + { "Insert", 0xd2 }, + { "Delete", 0xd3 }, + + {0,0} +}; + +const char *const ConsoleButtons[] = +{ + "mouse1", "mouse2", "mouse3", "mouse4", "mwheelup", + "mwheeldn", "mouse5", "mouse6", "mouse7", "mouse8" +}; + +static int32_t osdcmd_bind(osdfuncparm_t const * const parm) +{ + int32_t i, j, repeat; + + if (parm->numparms==1 && !Bstrcasecmp(parm->parms[0],"showkeys")) + { + for (i=0; ConsoleKeys[i].name; i++) + OSD_Printf("%s\n",ConsoleKeys[i].name); + for (i=0; i<MAXMOUSEBUTTONS; i++) + OSD_Printf("%s\n",ConsoleButtons[i]); + return OSDCMD_OK; + } + + if (parm->numparms==0) + { + int32_t j=0; + + OSD_Printf("Current key bindings:\n"); + + for (i=0; i<MAXBOUNDKEYS+MAXMOUSEBUTTONS; i++) + if (CONTROL_KeyIsBound(i)) + { + j++; + OSD_Printf("%-9s %s\"%s\"\n", CONTROL_KeyBinds[i].key, CONTROL_KeyBinds[i].repeat?"":"norepeat ", + CONTROL_KeyBinds[i].cmdstr); + } + + if (j == 0) + OSD_Printf("No binds found.\n"); + + return OSDCMD_OK; + } + + for (i=0; ConsoleKeys[i].name; i++) + if (!Bstrcasecmp(parm->parms[0],ConsoleKeys[i].name)) + break; + + if (!ConsoleKeys[i].name) + { + for (i=0; i<MAXMOUSEBUTTONS; i++) + if (!Bstrcasecmp(parm->parms[0],ConsoleButtons[i])) + break; + if (i >= MAXMOUSEBUTTONS) + return OSDCMD_SHOWHELP; + + if (parm->numparms < 2) + { + if (CONTROL_KeyBinds[MAXBOUNDKEYS + i].cmdstr && CONTROL_KeyBinds[MAXBOUNDKEYS + i ].key) + OSD_Printf("%-9s %s\"%s\"\n", ConsoleButtons[i], CONTROL_KeyBinds[MAXBOUNDKEYS + i].repeat?"":"norepeat ", + CONTROL_KeyBinds[MAXBOUNDKEYS + i].cmdstr); + else OSD_Printf("%s is unbound\n", ConsoleButtons[i]); + return OSDCMD_OK; + } + + j = 1; + + repeat = 1; + if (parm->numparms >= 2 && !Bstrcasecmp(parm->parms[j],"norepeat")) + { + repeat = 0; + j++; + } + + Bstrcpy(tempbuf,parm->parms[j++]); + for (; j<parm->numparms; j++) + { + Bstrcat(tempbuf," "); + Bstrcat(tempbuf,parm->parms[j++]); + } + + CONTROL_BindMouse(i, tempbuf, repeat, ConsoleButtons[i]); + + if (!OSD_ParsingScript()) + OSD_Printf("%s\n",parm->raw); + return OSDCMD_OK; + } + + if (parm->numparms < 2) + { + if (CONTROL_KeyIsBound(ConsoleKeys[i].id)) + OSD_Printf("%-9s %s\"%s\"\n", ConsoleKeys[i].name, CONTROL_KeyBinds[ConsoleKeys[i].id].repeat?"":"norepeat ", + CONTROL_KeyBinds[ConsoleKeys[i].id].cmdstr); + else OSD_Printf("%s is unbound\n", ConsoleKeys[i].name); + + return OSDCMD_OK; + } + + j = 1; + + repeat = 1; + if (parm->numparms >= 2 && !Bstrcasecmp(parm->parms[j],"norepeat")) + { + repeat = 0; + j++; + } + + Bstrcpy(tempbuf,parm->parms[j++]); + for (; j<parm->numparms; j++) + { + Bstrcat(tempbuf," "); + Bstrcat(tempbuf,parm->parms[j++]); + } + + CONTROL_BindKey(ConsoleKeys[i].id, tempbuf, repeat, ConsoleKeys[i].name); + + { + char *cp = tempbuf; + + // Populate the keyboard config menu based on the bind. + // Take care of processing one-to-many bindings properly, too. + while ((cp = Bstrstr(cp, "gamefunc_"))) + { + char *semi; + + cp += 9; // skip the "gamefunc_" + + semi = Bstrchr(cp, ';'); + if (semi) + *semi = 0; + + j = CONFIG_FunctionNameToNum(cp); + + if (semi) + cp = semi+1; + + if (j != -1) + { + ud.config.KeyboardKeys[j][1] = ud.config.KeyboardKeys[j][0]; + ud.config.KeyboardKeys[j][0] = ConsoleKeys[i].id; +// CONTROL_MapKey(j, ConsoleKeys[i].id, ud.config.KeyboardKeys[j][0]); + + if (j == gamefunc_Show_Console) + OSD_CaptureKey(ConsoleKeys[i].id); + } + } + } + + if (!OSD_ParsingScript()) + OSD_Printf("%s\n",parm->raw); + + return OSDCMD_OK; +} + +static int32_t osdcmd_unbindall(osdfuncparm_t const * const UNUSED(parm)) +{ + int32_t i; + + UNREFERENCED_CONST_PARAMETER(parm); + + for (i=0; i<MAXBOUNDKEYS; i++) + CONTROL_FreeKeyBind(i); + + for (i=0; i<MAXMOUSEBUTTONS; i++) + CONTROL_FreeMouseBind(i); + + for (i=0; i<NUMGAMEFUNCTIONS; i++) + { + ud.config.KeyboardKeys[i][0] = ud.config.KeyboardKeys[i][1] = 0xff; +// CONTROL_MapKey(i, ud.config.KeyboardKeys[i][0], ud.config.KeyboardKeys[i][1]); + } + + if (!OSD_ParsingScript()) + OSD_Printf("unbound all controls\n"); + + return OSDCMD_OK; +} + +static int32_t osdcmd_unbind(osdfuncparm_t const * const parm) +{ + int32_t i; + + if (parm->numparms < 1) return OSDCMD_SHOWHELP; + + for (i=0; ConsoleKeys[i].name; i++) + if (!Bstrcasecmp(parm->parms[0],ConsoleKeys[i].name)) + break; + + if (!ConsoleKeys[i].name) + { + for (i=0; i<MAXMOUSEBUTTONS; i++) + if (!Bstrcasecmp(parm->parms[0],ConsoleButtons[i])) + break; + + if (i >= MAXMOUSEBUTTONS) + return OSDCMD_SHOWHELP; + + CONTROL_FreeMouseBind(i); + + OSD_Printf("unbound %s\n",ConsoleButtons[i]); + + return OSDCMD_OK; + } + + CONTROL_FreeKeyBind(ConsoleKeys[i].id); + + OSD_Printf("unbound key %s\n",ConsoleKeys[i].name); + + return OSDCMD_OK; +} + +static int32_t osdcmd_quicksave(osdfuncparm_t const * const UNUSED(parm)) +{ + UNREFERENCED_CONST_PARAMETER(parm); + if (!(g_player[myconnectindex].ps->gm & MODE_GAME)) + OSD_Printf("quicksave: not in a game.\n"); + else g_doQuickSave = 1; + return OSDCMD_OK; +} + +static int32_t osdcmd_quickload(osdfuncparm_t const * const UNUSED(parm)) +{ + UNREFERENCED_CONST_PARAMETER(parm); + if (!(g_player[myconnectindex].ps->gm & MODE_GAME)) + OSD_Printf("quickload: not in a game.\n"); + else g_doQuickSave = 2; + return OSDCMD_OK; +} + +static int32_t osdcmd_screenshot(osdfuncparm_t const * const parm) +{ +// KB_ClearKeysDown(); +#ifndef EDUKE32_STANDALONE + static const char *fn = "duke0000.png"; +#else + static const char *fn = "capt0000.png"; +#endif + + if (parm->numparms == 1 && !Bstrcasecmp(parm->parms[0], "tga")) + screencapture_tga(fn, 0); + else screencapture(fn, 0); + + return OSDCMD_OK; +} + +#if 0 +static int32_t osdcmd_savestate(osdfuncparm_t const * const UNUSED(parm)) +{ + UNREFERENCED_PARAMETER(parm); + G_SaveMapState(); + return OSDCMD_OK; +} + +static int32_t osdcmd_restorestate(osdfuncparm_t const * const UNUSED(parm)) +{ + UNREFERENCED_PARAMETER(parm); + G_RestoreMapState(); + return OSDCMD_OK; +} +#endif + +#ifdef DEBUGGINGAIDS +static int32_t osdcmd_inittimer(osdfuncparm_t const * const parm) +{ + if (parm->numparms != 1) + { + OSD_Printf("%dHz timer\n",g_timerTicsPerSecond); + return OSDCMD_SHOWHELP; + } + + G_InitTimer(Batol(parm->parms[0])); + + OSD_Printf("%s\n",parm->raw); + return OSDCMD_OK; +} +#endif + +static int32_t osdcmd_disconnect(osdfuncparm_t const * const UNUSED(parm)) +{ + UNREFERENCED_CONST_PARAMETER(parm); + g_netDisconnect = 1; + return OSDCMD_OK; +} + +static int32_t osdcmd_connect(osdfuncparm_t const * const parm) +{ + if (parm->numparms != 1) + return OSDCMD_SHOWHELP; + + Net_Connect(parm->parms[0]); + G_BackToMenu(); + return OSDCMD_OK; +} + +static int32_t osdcmd_password(osdfuncparm_t const * const parm) +{ + if (parm->numparms < 1) + { + Bmemset(g_netPassword, 0, sizeof(g_netPassword)); + return OSDCMD_OK; + } + Bstrncpy(g_netPassword, (parm->raw) + 9, sizeof(g_netPassword)-1); + + return OSDCMD_OK; +} + +#if !defined NETCODE_DISABLE +static int32_t osdcmd_listplayers(osdfuncparm_t const * const parm) +{ + ENetPeer *currentPeer; + char ipaddr[32]; + + if (parm && parm->numparms != 0) + return OSDCMD_SHOWHELP; + + if (!g_netServer) + { + initprintf("You are not the server.\n"); + return OSDCMD_OK; + } + + initprintf("Connected clients:\n"); + + for (currentPeer = g_netServer -> peers; + currentPeer < & g_netServer -> peers [g_netServer -> peerCount]; + ++ currentPeer) + { + if (currentPeer -> state != ENET_PEER_STATE_CONNECTED) + continue; + + enet_address_get_host_ip(¤tPeer->address, ipaddr, sizeof(ipaddr)); + initprintf("%x %s %s\n", currentPeer->address.host, ipaddr, + g_player[(intptr_t)currentPeer->data].user_name); + } + + return OSDCMD_OK; +} + +static int32_t osdcmd_kick(osdfuncparm_t const * const parm) +{ + ENetPeer *currentPeer; + uint32_t hexaddr; + + if (parm->numparms != 1) + return OSDCMD_SHOWHELP; + + if (!g_netServer) + { + initprintf("You are not the server.\n"); + return OSDCMD_OK; + } + + for (currentPeer = g_netServer -> peers; + currentPeer < & g_netServer -> peers [g_netServer -> peerCount]; + ++ currentPeer) + { + if (currentPeer -> state != ENET_PEER_STATE_CONNECTED) + continue; + + sscanf(parm->parms[0],"%" SCNx32 "", &hexaddr); + + if (currentPeer->address.host == hexaddr) + { + initprintf("Kicking %x (%s)\n", currentPeer->address.host, + g_player[(intptr_t)currentPeer->data].user_name); + enet_peer_disconnect(currentPeer, DISC_KICKED); + return OSDCMD_OK; + } + } + + initprintf("Player %s not found!\n", parm->parms[0]); + osdcmd_listplayers(NULL); + + return OSDCMD_OK; +} + +static int32_t osdcmd_kickban(osdfuncparm_t const * const parm) +{ + ENetPeer *currentPeer; + uint32_t hexaddr; + + if (parm->numparms != 1) + return OSDCMD_SHOWHELP; + + if (!g_netServer) + { + initprintf("You are not the server.\n"); + return OSDCMD_OK; + } + + for (currentPeer = g_netServer -> peers; + currentPeer < & g_netServer -> peers [g_netServer -> peerCount]; + ++ currentPeer) + { + if (currentPeer -> state != ENET_PEER_STATE_CONNECTED) + continue; + + sscanf(parm->parms[0],"%" SCNx32 "", &hexaddr); + + // TODO: implement banning logic + + if (currentPeer->address.host == hexaddr) + { + char ipaddr[32]; + + enet_address_get_host_ip(¤tPeer->address, ipaddr, sizeof(ipaddr)); + initprintf("Host %s is now banned.\n", ipaddr); + initprintf("Kicking %x (%s)\n", currentPeer->address.host, + g_player[(intptr_t)currentPeer->data].user_name); + enet_peer_disconnect(currentPeer, DISC_BANNED); + return OSDCMD_OK; + } + } + + initprintf("Player %s not found!\n", parm->parms[0]); + osdcmd_listplayers(NULL); + + return OSDCMD_OK; +} +#endif + +static int32_t osdcmd_cvar_set_game(osdfuncparm_t const * const parm) +{ + int32_t r = osdcmd_cvar_set(parm); + + if (r != OSDCMD_OK) return r; + + if (!Bstrcasecmp(parm->name, "r_size")) + { + ud.statusbarmode = (ud.screen_size < 8); + G_UpdateScreenArea(); + } + else if (!Bstrcasecmp(parm->name, "r_maxfps")) + { + if (r_maxfps != 0) r_maxfps = clamp(r_maxfps, 30, 1000); + g_frameDelay = r_maxfps ? (getu64tickspersec()/r_maxfps) : 0; + } + else if (!Bstrcasecmp(parm->name, "r_ambientlight")) + { + if (r_ambientlight == 0) + r_ambientlightrecip = 256.f; + else r_ambientlightrecip = 1.f/r_ambientlight; + } + else if (!Bstrcasecmp(parm->name, "in_mouse")) + { + CONTROL_MouseEnabled = (ud.config.UseMouse && CONTROL_MousePresent); + } + else if (!Bstrcasecmp(parm->name, "in_joystick")) + { + CONTROL_JoystickEnabled = (ud.config.UseJoystick && CONTROL_JoyPresent); + } + else if (!Bstrcasecmp(parm->name, "vid_gamma")) + { + ud.brightness = GAMMA_CALC; + ud.brightness <<= 2; + setbrightness(ud.brightness>>2,g_player[myconnectindex].ps->palette,0); + } + else if (!Bstrcasecmp(parm->name, "vid_brightness") || !Bstrcasecmp(parm->name, "vid_contrast")) + { + setbrightness(ud.brightness>>2,g_player[myconnectindex].ps->palette,0); + } + else if (!Bstrcasecmp(parm->name, "hud_scale") + || !Bstrcasecmp(parm->name, "hud_statusbarmode") + || !Bstrcasecmp(parm->name, "r_rotatespritenowidescreen")) + { + G_UpdateScreenArea(); + } + else if (!Bstrcasecmp(parm->name, "skill")) + { + if (numplayers > 1) + return r; + + ud.player_skill = ud.m_player_skill; + } + else if (!Bstrcasecmp(parm->name, "color")) + { + G_CheckPlayerColor((int32_t *)&ud.color,-1); + g_player[0].ps->palookup = g_player[0].pcolor = ud.color; + } + else if (!Bstrcasecmp(parm->name, "osdscale")) + { + osdrscale = 1.f/osdscale; + + if (xdim && ydim) + OSD_ResizeDisplay(xdim, ydim); + } + else if (!Bstrcasecmp(parm->name, "wchoice")) + { + if (parm->numparms == 1) + { + if (g_forceWeaponChoice) // rewrite ud.wchoice because osdcmd_cvar_set already changed it + { + int j = 0; + + while (j < 10) + { + ud.wchoice[j] = g_player[myconnectindex].wchoice[j] + '0'; + j++; + } + + ud.wchoice[j] = 0; + } + else + { + char const *c = parm->parms[0]; + + if (*c) + { + int j = 0; + + while (*c && j < 10) + { + g_player[myconnectindex].wchoice[j] = *c - '0'; + c++; + j++; + } + + while (j < 10) + { + if (j == 9) + g_player[myconnectindex].wchoice[9] = 1; + else + g_player[myconnectindex].wchoice[j] = 2; + + j++; + } + } + } + + g_forceWeaponChoice = 0; + } + + /* Net_SendClientInfo();*/ + } + + return r; +} + +static int32_t osdcmd_cvar_set_multi(osdfuncparm_t const * const parm) +{ + int32_t r = osdcmd_cvar_set_game(parm); + + if (r != OSDCMD_OK) return r; + + G_UpdatePlayerFromMenu(); + + return r; +} + +int32_t registerosdcommands(void) +{ + uint32_t i; + + static osdcvardata_t cvars_game[] = + { + { "crosshair", "enable/disable crosshair", (void *)&ud.crosshair, CVAR_BOOL, 0, 1 }, + + { "cl_autoaim", "enable/disable weapon autoaim", (void *)&ud.config.AutoAim, CVAR_INT|CVAR_MULTI, 0, 3 }, + { "cl_automsg", "enable/disable automatically sending messages to all players", (void *)&ud.automsg, CVAR_BOOL, 0, 1 }, + { "cl_autorun", "enable/disable autorun", (void *)&ud.auto_run, CVAR_BOOL, 0, 1 }, + { "cl_autovote", "enable/disable automatic voting", (void *)&ud.autovote, CVAR_INT, 0, 2 }, + + { "cl_cheatmask", "configure what cheats show in the cheats menu", (void *)&cl_cheatmask, CVAR_UINT, 0, ~0 }, + + { "cl_obituaries", "enable/disable multiplayer death messages", (void *)&ud.obituaries, CVAR_BOOL, 0, 1 }, + { "cl_democams", "enable/disable demo playback cameras", (void *)&ud.democams, CVAR_BOOL, 0, 1 }, + + { "cl_idplayers", "enable/disable name display when aiming at opponents", (void *)&ud.idplayers, CVAR_BOOL, 0, 1 }, + + { "cl_runmode", "enable/disable modernized run key operation", (void *)&ud.runkey_mode, CVAR_BOOL, 0, 1 }, + + { "cl_showcoords", "show your position in the game world", (void *)&ud.coords, CVAR_INT, 0, +#ifdef USE_OPENGL + 2 +#else + 1 +#endif + }, + + { "cl_viewbob", "enable/disable player head bobbing", (void *)&ud.viewbob, CVAR_BOOL, 0, 1 }, + + { "cl_weaponsway", "enable/disable player weapon swaying", (void *)&ud.weaponsway, CVAR_BOOL, 0, 1 }, + { "cl_weaponswitch", "enable/disable auto weapon switching", (void *)&ud.weaponswitch, CVAR_INT|CVAR_MULTI, 0, 7 }, + + { "color", "changes player palette", (void *)&ud.color, CVAR_INT|CVAR_MULTI, 0, MAXPALOOKUPS-1 }, + + { "crosshairscale","changes the size of the crosshair", (void *)&ud.crosshairscale, CVAR_INT, 10, 100 }, + + { "demorec_diffs","enable/disable diff recording in demos",(void *)&demorec_diffs_cvar, CVAR_BOOL, 0, 1 }, + { "demorec_force","enable/disable forced demo recording",(void *)&demorec_force_cvar, CVAR_BOOL|CVAR_NOSAVE, 0, 1 }, + { + "demorec_difftics","sets game tic interval after which a diff is recorded", + (void *)&demorec_difftics_cvar, CVAR_INT, 2, 60*REALGAMETICSPERSEC + }, + { "demorec_diffcompress","Compression method for diffs. (0: none, 1: KSLZW)",(void *)&demorec_diffcompress_cvar, CVAR_INT, 0, 1 }, + { "demorec_synccompress","Compression method for input. (0: none, 1: KSLZW)",(void *)&demorec_synccompress_cvar, CVAR_INT, 0, 1 }, + { "demorec_seeds","enable/disable recording of random seed for later sync checking",(void *)&demorec_seeds_cvar, CVAR_BOOL, 0, 1 }, + { "demoplay_diffs","enable/disable application of diffs in demo playback",(void *)&demoplay_diffs, CVAR_BOOL, 0, 1 }, + { "demoplay_showsync","enable/disable display of sync status",(void *)&demoplay_showsync, CVAR_BOOL, 0, 1 }, + + { "hud_althud", "enable/disable alternate mini-hud", (void *)&ud.althud, CVAR_INT, 0, 2 }, + { "hud_bgstretch", "enable/disable background image stretching in wide resolutions", (void *)&ud.bgstretch, CVAR_BOOL, 0, 1 }, + { "hud_messagetime", "length of time to display multiplayer chat messages", (void *)&ud.msgdisptime, CVAR_INT, 0, 3600 }, + { "hud_numbertile", "first tile in alt hud number set", (void *)&althud_numbertile, CVAR_INT, 0, MAXUSERTILES-10 }, + { "hud_numberpal", "pal for alt hud numbers", (void *)&althud_numberpal, CVAR_INT, 0, MAXPALOOKUPS-1 }, + { "hud_shadows", "enable/disable althud shadows", (void *)&althud_shadows, CVAR_BOOL, 0, 1 }, + { "hud_flashing", "enable/disable althud flashing", (void *)&althud_flashing, CVAR_BOOL, 0, 1 }, + { "hud_glowingquotes", "enable/disable \"glowing\" quote text", (void *)&hud_glowingquotes, CVAR_BOOL, 0, 1 }, + { "hud_scale","changes the hud scale", (void *)&ud.statusbarscale, CVAR_INT|CVAR_FUNCPTR, 36, 100 }, + { "hud_showmapname", "enable/disable map name display on load", (void *)&hud_showmapname, CVAR_BOOL, 0, 1 }, + { "hud_stats", "enable/disable level statistics display", (void *)&ud.levelstats, CVAR_BOOL, 0, 1 }, + { "hud_textscale", "sets multiplayer chat message size", (void *)&ud.textscale, CVAR_INT, 100, 400 }, + { "hud_weaponscale","changes the weapon scale", (void *)&ud.weaponscale, CVAR_INT, 10, 100 }, + { "hud_statusbarmode", "change overlay mode of status bar", (void *)&ud.statusbarmode, CVAR_BOOL|CVAR_FUNCPTR, 0, 1 }, + +#ifdef EDUKE32_TOUCH_DEVICES + { "hud_hidestick", "hide the touch input stick", (void *)&droidinput.hideStick, CVAR_BOOL, 0, 1 }, +#endif + + { "in_joystick","enables input from the joystick if it is present",(void *)&ud.config.UseJoystick, CVAR_BOOL|CVAR_FUNCPTR, 0, 1 }, + { "in_mouse","enables input from the mouse if it is present",(void *)&ud.config.UseMouse, CVAR_BOOL|CVAR_FUNCPTR, 0, 1 }, + + { "in_aimmode", "0:toggle, 1:hold to aim", (void *)&ud.mouseaiming, CVAR_BOOL, 0, 1 }, + { + "in_mousebias", "emulates the original mouse code's weighting of input towards whichever axis is moving the most at any given time", + (void *)&ud.config.MouseBias, CVAR_INT, 0, 32 + }, + { "in_mousedeadzone", "amount of mouse movement to filter out", (void *)&ud.config.MouseDeadZone, CVAR_INT, 0, 512 }, + { "in_mouseflip", "invert vertical mouse movement", (void *)&ud.mouseflip, CVAR_BOOL, 0, 1 }, + { "in_mousemode", "toggles vertical mouse view", (void *)&g_myAimMode, CVAR_BOOL, 0, 1 }, + { "in_mousesmoothing", "enable/disable mouse input smoothing", (void *)&ud.config.SmoothInput, CVAR_BOOL, 0, 1 }, + + { "mus_enabled", "enables/disables music", (void *)&ud.config.MusicToggle, CVAR_BOOL, 0, 1 }, + { "mus_volume", "controls music volume", (void *)&ud.config.MusicVolume, CVAR_INT, 0, 255 }, + + { "osdhightile", "enable/disable hires art replacements for console text", (void *)&osdhightile, CVAR_BOOL, 0, 1 }, + { "osdscale", "adjust console text size", (void *)&osdscale, CVAR_FLOAT|CVAR_FUNCPTR, 1, 4 }, + + { "r_camrefreshdelay", "minimum delay between security camera sprite updates, 120 = 1 second", (void *)&ud.camera_time, CVAR_INT, 1, 240 }, + { "r_drawweapon", "enable/disable weapon drawing", (void *)&ud.drawweapon, CVAR_INT, 0, 2 }, + { "r_showfps", "show the frame rate counter", (void *)&ud.showfps, CVAR_INT, 0, 2 }, + { "r_shadows", "enable/disable sprite and model shadows", (void *)&ud.shadows, CVAR_BOOL, 0, 1 }, + { "r_size", "change size of viewable area", (void *)&ud.screen_size, CVAR_INT|CVAR_FUNCPTR, 0, 64 }, + { "r_rotatespritenowidescreen", "pass bit 1024 to all CON rotatesprite calls", (void *)&g_rotatespriteNoWidescreen, CVAR_BOOL|CVAR_FUNCPTR, 0, 1 }, + { "r_pixeldoubling", "enable/disable pixel doubling in the software renderer", (void *) &ud.detail, CVAR_BOOL, 0, 1 }, + { "r_precache", "enable/disable the pre-level caching routine", (void *)&ud.config.useprecache, CVAR_BOOL, 0, 1 }, + + { "r_ambientlight", "sets the global map light level",(void *)&r_ambientlight, CVAR_FLOAT|CVAR_FUNCPTR, 0, 10 }, + { "r_maxfps", "limit the frame rate",(void *)&r_maxfps, CVAR_INT|CVAR_FUNCPTR, 0, 1000 }, + + { "sensitivity","changes the mouse sensitivity", (void *)&CONTROL_MouseSensitivity, CVAR_FLOAT|CVAR_FUNCPTR, 0, 25 }, + + { "skill","changes the game skill setting", (void *)&ud.m_player_skill, CVAR_INT|CVAR_FUNCPTR/*|CVAR_NOMULTI*/, 0, 5 }, + + { "snd_ambience", "enables/disables ambient sounds", (void *)&ud.config.AmbienceToggle, CVAR_BOOL, 0, 1 }, + { "snd_enabled", "enables/disables sound effects", (void *)&ud.config.SoundToggle, CVAR_BOOL, 0, 1 }, + { "snd_mastervolume", "master volume for sound system", (void *)&ud.config.MasterVolume, CVAR_INT, 0, 255 }, + { "snd_fxvolume", "volume of sound effects", (void *)&ud.config.FXVolume, CVAR_INT, 1, 255 }, + { "snd_mixrate", "sound mixing rate", (void *)&ud.config.MixRate, CVAR_INT, 0, 48000 }, + { "snd_numchannels", "the number of sound channels", (void *)&ud.config.NumChannels, CVAR_INT, 0, 2 }, + { "snd_numvoices", "the number of concurrent sounds", (void *)&ud.config.NumVoices, CVAR_INT, 0, 256 }, + { "snd_fluidsynthbackend", "the backend to use with fluidsynth", (void *)&ud.config.FluidsynthBackend, CVAR_INT, 0, 9 }, + { "snd_reversestereo", "reverses the stereo channels", (void *)&ud.config.ReverseStereo, CVAR_BOOL, 0, 1 }, + { "snd_speech", "enables/disables player speech", (void *)&ud.config.VoiceToggle, CVAR_INT, 0, 5 }, + + { "team","change team in multiplayer", (void *)&ud.team, CVAR_INT|CVAR_MULTI, 0, 3 }, + +#ifdef EDUKE32_TOUCH_DEVICES + { "touch_sens_move_x","touch input sensitivity for moving forward/back", (void *)&droidinput.forward_sens, CVAR_FLOAT, 1, 9 }, + { "touch_sens_move_y","touch input sensitivity for strafing", (void *)&droidinput.strafe_sens, CVAR_FLOAT, 1, 9 }, + { "touch_sens_look_x", "touch input sensitivity for turning left/right", (void *) &droidinput.yaw_sens, CVAR_FLOAT, 1, 9 }, + { "touch_sens_look_y", "touch input sensitivity for looking up/down", (void *) &droidinput.pitch_sens, CVAR_FLOAT, 1, 9 }, + { "touch_invert", "invert look up/down touch input", (void *) &droidinput.invertLook, CVAR_INT, 0, 1 }, +#endif + + { "vid_gamma","adjusts gamma component of gamma ramp",(void *)&vid_gamma, CVAR_FLOAT|CVAR_FUNCPTR, 0, 10 }, + { "vid_contrast","adjusts contrast component of gamma ramp",(void *)&vid_contrast, CVAR_FLOAT|CVAR_FUNCPTR, 0, 10 }, + { "vid_brightness","adjusts brightness component of gamma ramp",(void *)&vid_brightness, CVAR_FLOAT|CVAR_FUNCPTR, 0, 10 }, + { "wchoice","sets weapon autoselection order", (void *)ud.wchoice, CVAR_STRING|CVAR_FUNCPTR, 0, MAX_WEAPONS }, + }; + + osdcmd_cheatsinfo_stat.cheatnum = -1; + + for (i=0; i<ARRAY_SIZE(cvars_game); i++) + { + switch (cvars_game[i].flags & (CVAR_FUNCPTR|CVAR_MULTI)) + { + case CVAR_FUNCPTR: + OSD_RegisterCvar(&cvars_game[i], osdcmd_cvar_set_game); break; + case CVAR_MULTI: + case CVAR_FUNCPTR|CVAR_MULTI: + OSD_RegisterCvar(&cvars_game[i], osdcmd_cvar_set_multi); break; + default: + OSD_RegisterCvar(&cvars_game[i], osdcmd_cvar_set); break; + } + } + + if (VOLUMEONE) + OSD_RegisterFunction("changelevel","changelevel <level>: warps to the given level", osdcmd_changelevel); + else + { + OSD_RegisterFunction("changelevel","changelevel <volume> <level>: warps to the given level", osdcmd_changelevel); + OSD_RegisterFunction("map","map <mapfile>: loads the given user map", osdcmd_map); + OSD_RegisterFunction("demo","demo <demofile or demonum>: starts the given demo", osdcmd_demo); + } + + OSD_RegisterFunction("addpath","addpath <path>: adds path to game filesystem", osdcmd_addpath); + OSD_RegisterFunction("bind","bind <key> <string>: associates a keypress with a string of console input. Type \"bind showkeys\" for a list of keys and \"listsymbols\" for a list of valid console commands.", osdcmd_bind); + OSD_RegisterFunction("cmenu","cmenu <#>: jumps to menu", osdcmd_cmenu); + OSD_RegisterFunction("crosshaircolor","crosshaircolor: changes the crosshair color", osdcmd_crosshaircolor); + + OSD_RegisterFunction("connect","connect: connects to a multiplayer game", osdcmd_connect); + OSD_RegisterFunction("disconnect","disconnect: disconnects from the local multiplayer game", osdcmd_disconnect); + + for (i=0; i<NUMGAMEFUNCTIONS; i++) + { + char *t; + int32_t j; + + if (gamefunctions[i][0] == '\0') + continue; + +// if (!Bstrcmp(gamefunctions[i],"Show_Console")) continue; + + Bsprintf(tempbuf,"gamefunc_%s",gamefunctions[i]); + t = Xstrdup(tempbuf); + for (j=Bstrlen(t); j>=0; j--) + t[j] = Btolower(t[j]); + Bstrcat(tempbuf,": game button"); + OSD_RegisterFunction(t, Xstrdup(tempbuf), osdcmd_button); + } + + OSD_RegisterFunction("give","give <all|health|weapons|ammo|armor|keys|inventory>: gives requested item", osdcmd_give); + OSD_RegisterFunction("god","god: toggles god mode", osdcmd_god); + OSD_RegisterFunction("activatecheat","activatecheat <id>: activates a cheat code", osdcmd_activatecheat); + + OSD_RegisterFunction("initgroupfile","initgroupfile <path>: adds a grp file into the game filesystem", osdcmd_initgroupfile); +#ifdef DEBUGGINGAIDS + OSD_RegisterFunction("inittimer","debug", osdcmd_inittimer); +#endif +#if !defined NETCODE_DISABLE + OSD_RegisterFunction("kick","kick <id>: kicks a multiplayer client. See listplayers.", osdcmd_kick); + OSD_RegisterFunction("kickban","kickban <id>: kicks a multiplayer client and prevents them from reconnecting. See listplayers.", osdcmd_kickban); + + OSD_RegisterFunction("listplayers","listplayers: lists currently connected multiplayer clients", osdcmd_listplayers); +#endif + OSD_RegisterFunction("music","music E<ep>L<lev>: change music", osdcmd_music); + OSD_RegisterFunction("name","name: change your multiplayer nickname", osdcmd_name); + OSD_RegisterFunction("noclip","noclip: toggles clipping mode", osdcmd_noclip); + + OSD_RegisterFunction("password","password: sets multiplayer game password", osdcmd_password); + + OSD_RegisterFunction("quicksave","quicksave: performs a quick save", osdcmd_quicksave); + OSD_RegisterFunction("quickload","quickload: performs a quick load", osdcmd_quickload); + OSD_RegisterFunction("quit","quit: exits the game immediately", osdcmd_quit); + OSD_RegisterFunction("exit","exit: exits the game immediately", osdcmd_quit); + + OSD_RegisterFunction("restartmap", "restartmap: restarts the current map", osdcmd_restartmap); + OSD_RegisterFunction("restartsound","restartsound: reinitializes the sound system",osdcmd_restartsound); + OSD_RegisterFunction("restartvid","restartvid: reinitializes the video mode",osdcmd_restartvid); +#if !defined LUNATIC + OSD_RegisterFunction("addlogvar","addlogvar <gamevar>: prints the value of a gamevar", osdcmd_addlogvar); + OSD_RegisterFunction("setvar","setvar <gamevar> <value>: sets the value of a gamevar", osdcmd_setvar); + OSD_RegisterFunction("setvarvar","setvarvar <gamevar1> <gamevar2>: sets the value of <gamevar1> to <gamevar2>", osdcmd_setvar); + OSD_RegisterFunction("setactorvar","setactorvar <actor#> <gamevar> <value>: sets the value of <actor#>'s <gamevar> to <value>", osdcmd_setactorvar); +#else + OSD_RegisterFunction("lua", "lua \"Lua code...\": runs Lunatic code", osdcmd_lua); +#endif + OSD_RegisterFunction("screenshot","screenshot [format]: takes a screenshot.", osdcmd_screenshot); + + OSD_RegisterFunction("spawn","spawn <picnum> [palnum] [cstat] [ang] [x y z]: spawns a sprite with the given properties",osdcmd_spawn); + + OSD_RegisterFunction("unbind","unbind <key>: unbinds a key", osdcmd_unbind); + OSD_RegisterFunction("unbindall","unbindall: unbinds all keys", osdcmd_unbindall); + + OSD_RegisterFunction("vidmode","vidmode <xdim> <ydim> <bpp> <fullscreen>: change the video mode",osdcmd_vidmode); +#ifdef USE_OPENGL + baselayer_osdcmd_vidmode_func = osdcmd_vidmode; +#endif + return 0; +} + diff --git a/patch/sdlmusic.cpp b/patch/sdlmusic.cpp new file mode 100644 index 0000000..62af485 --- /dev/null +++ b/patch/sdlmusic.cpp @@ -0,0 +1,180 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2018 Jakob L. Kreuze + +This file is part of EDuke32. + +EDuke32 is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- + +/* + * Replacement for the SDL2_Mixer reimplementation of Jim Dose's FX_MAN + * routines, for anyone troubled by the shitty internal version of TiMidity. + */ + +#include "fluidsynth_inc.h" + +#include "duke3d.h" +#include "game.h" +#include "music.h" +#include "midi.h" + +static bool music_initialized = false; + +static fluid_synth_t *synth; +static fluid_settings_t *settings; +static fluid_player_t *player; +static fluid_audio_driver_t *driver; + +static const char *backend_names[10] = { + "unknown", + "jack", + "alsa", + "oss", + "pulseaudio", + "coreaudio", + "dsound", + "portaudio", + "sndman", + "dart", +}; + + +const char *MUSIC_ErrorString(int32_t ErrorNumber) +{ + switch (ErrorNumber) + { + default: + return "Unknown error."; + } + + return NULL; +} + +int32_t MUSIC_Init(int32_t SoundCard, int32_t Address) +{ + int32_t backend = ud.config.FluidsynthBackend; + + if (getenv("EDUKE32_MUSIC_CMD")) { + initprintf("External MIDI player not supported by Fluidsynth backend"); + } + + if (music_initialized) { + // Music system is already initialized + // return MUSIC_Error; + return MUSIC_Ok; + } + + settings = new_fluid_settings(); + + MUSIC_SetVolume(ud.config.MusicVolume); + + if (backend) { + fluid_settings_setstr(settings, "audio.driver", backend_names[backend]); + } + + synth = new_fluid_synth(settings); + player = new_fluid_player(synth); + driver = new_fluid_audio_driver(settings, synth); + + fluid_synth_sfload(synth, "/usr/share/soundfonts/FluidR3_GM.sf2", 1); + + return MIDI_Ok; +} + +int32_t MUSIC_Shutdown(void) +{ + delete_fluid_player(player); + delete_fluid_audio_driver(driver); + delete_fluid_synth(synth); + delete_fluid_settings(settings); + + music_initialized = false; + return MUSIC_Ok; +} + +void MUSIC_SetVolume(int32_t volume) +{ + double val = ((double) volume) * 0.00390625; + fluid_settings_setnum(settings, "synth.gain", val); +} + +int32_t MUSIC_GetVolume(void) +{ + double val; + fluid_settings_getnum(settings, "synth.gain", &val); + return (int32_t) (val / 0.00390625); +} + +void MUSIC_SetLoopFlag(int32_t loopflag) +{ + fluid_player_set_loop(player, loopflag == MUSIC_LoopSong ? -1 : 1); +} + +void MUSIC_Continue(void) +{ + fluid_player_play(player); +} + +void MUSIC_Pause(void) +{ + fluid_player_stop(player); +} + +int32_t MUSIC_StopSong(void) +{ + fluid_player_stop(player); + MUSIC_Shutdown(); + MUSIC_Init(0, 0); + return MUSIC_Ok; +} + +// Fun fact to any onlookers: The part that parses the MIDI file and +// figures out how long it is worked on the first try and it's a format +// I've never worked with before. I celebrated. +int32_t MUSIC_PlaySong(char *song, int32_t loopflag) +{ + char *tracks; + size_t file_size; + uint16_t num_tracks; + + tracks = song + 0x14; + num_tracks = *((uint16_t *) (song + 0x10)); + file_size = 0x14; // Size of the header. + + while (num_tracks--) { + uint16_t track_size; + + if (!memcmp(tracks, "MTrk", 4)) { + break; + } + + track_size = *((uint16_t *) (tracks + 0x04)); + file_size += track_size + 0x08; + tracks += track_size + 0x08; + } + + fluid_player_add_mem(player, song, file_size); + fluid_player_play(player); + return MUSIC_Ok; +} + +int32_t MUSIC_InitMidi(int32_t card, midifuncs *Funcs, int32_t Address) +{ + return MIDI_Ok; +} + +void MUSIC_Update(void) {} |