From e36f86b6b08137aae617797302a7a6be5093eed5 Mon Sep 17 00:00:00 2001 From: jaih1r0 <146522416+jaih1r0@users.noreply.github.com> Date: Sat, 17 Aug 2024 20:28:16 -0600 Subject: [PATCH] implementing Gearbox yes --- CVARINFO | 60 ++ GLDEFS | 7 + GRAPHICS/Gearbox/gb_ammo.png | Bin 0 -> 81 bytes GRAPHICS/Gearbox/gb_box.png | Bin 0 -> 1127 bytes GRAPHICS/Gearbox/gb_circ.png | Bin 0 -> 101919 bytes GRAPHICS/Gearbox/gb_cor.png | Bin 0 -> 80 bytes GRAPHICS/Gearbox/gb_desc.png | Bin 0 -> 13115 bytes GRAPHICS/Gearbox/gb_hand.png | Bin 0 -> 8053 bytes GRAPHICS/Gearbox/gb_hcir.png | Bin 0 -> 65748 bytes GRAPHICS/Gearbox/gb_nope.png | Bin 0 -> 1599 bytes GRAPHICS/Gearbox/gb_pip.png | Bin 0 -> 99 bytes GRAPHICS/Gearbox/gb_pntr.png | Bin 0 -> 5565 bytes GRAPHICS/Gearbox/gb_weap.png | Bin 0 -> 1746 bytes GRAPHICS/Gearbox/gb_wpsel.png | Bin 0 -> 1862 bytes KEYCONF.txt | 44 +- MENUDEF.txt | 193 ++++- SOUNDS/gb_nope.ogg | Bin 0 -> 5253 bytes SOUNDS/gb_tick.ogg | Bin 0 -> 5766 bytes SOUNDS/gb_toggle.ogg | Bin 0 -> 22419 bytes ZSCRIPT.zc | 87 +- language.gearbox | 102 +++ shaders/mfx_bss_blur.fp | 31 + sndinfo.gearbox | 11 + zmapinfo.txt | 4 +- zscript/MD5/MD5.zs | 196 +++++ zscript/gearbox/EquipmentMenu.zs | 111 +++ zscript/gearbox/activity.zs | 72 ++ zscript/gearbox/changer.zs | 89 ++ .../gearbox/custom_weapon_order_storage.zs | 186 +++++ zscript/gearbox/display/blocky_view.zs | 518 ++++++++++++ zscript/gearbox/display/blur.zs | 27 + zscript/gearbox/display/caption.zs | 47 ++ zscript/gearbox/display/dim.zs | 29 + zscript/gearbox/display/fade_in_out.zs | 43 + zscript/gearbox/display/text_view.zs | 191 +++++ zscript/gearbox/engine/level.zs | 35 + zscript/gearbox/engine/player.zs | 27 + zscript/gearbox/engine/weapon_watcher.zs | 40 + zscript/gearbox/event_handler.zs | 632 ++++++++++++++ zscript/gearbox/event_processor.zs | 40 + zscript/gearbox/font_selector.zs | 53 ++ zscript/gearbox/freezer.zs | 129 +++ zscript/gearbox/gearboxInfo/Readme.gearbox | 83 ++ zscript/gearbox/gearboxInfo/changelog.gearbox | 333 ++++++++ zscript/gearbox/gearboxInfo/copying.gearbox | 674 +++++++++++++++ zscript/gearbox/gearboxInfo/credits.gearbox | 16 + zscript/gearbox/input.zs | 58 ++ zscript/gearbox/input_processor.zs | 55 ++ zscript/gearbox/inventory_menu.zs | 143 ++++ zscript/gearbox/inventory_user.zs | 80 ++ zscript/gearbox/netevent_processor.zs | 59 ++ zscript/gearbox/options.zs | 173 ++++ zscript/gearbox/pb/tokens.zs | 784 ++++++++++++++++++ zscript/gearbox/printer.zs | 40 + zscript/gearbox/sender.zs | 56 ++ zscript/gearbox/service/hide_service.zs | 41 + zscript/gearbox/service/icon_service.zs | 66 ++ zscript/gearbox/service/service.zs | 126 +++ zscript/gearbox/sounds.zs | 62 ++ zscript/gearbox/specials_menu.zs | 140 ++++ zscript/gearbox/tools/ammo.zs | 27 + zscript/gearbox/tools/cvar.zs | 62 ++ zscript/gearbox/tools/log.zs | 58 ++ zscript/gearbox/tools/texture_cache.zs | 71 ++ zscript/gearbox/view_model.zs | 37 + zscript/gearbox/weapon_data.zs | 24 + zscript/gearbox/weapon_data_loader.zs | 154 ++++ zscript/gearbox/weapon_menu.zs | 444 ++++++++++ zscript/gearbox/wheel/controller.zs | 102 +++ zscript/gearbox/wheel/controller_model.zs | 24 + zscript/gearbox/wheel/indexer.zs | 237 ++++++ zscript/gearbox/wheel/inner_indexer.zs | 33 + zscript/gearbox/wheel/multiwheel.zs | 66 ++ zscript/gearbox/wheel/multiwheel_mode.zs | 40 + zscript/gearbox/wheel/screen.zs | 69 ++ zscript/gearbox/wheel/text.zs | 137 +++ zscript/gearbox/wheel/view.zs | 577 +++++++++++++ zscript/m_gizmos/previous_weapon.zs | 119 +++ zscript/zabor/event_handler.zs | 326 ++++++++ 79 files changed, 8593 insertions(+), 7 deletions(-) create mode 100644 GRAPHICS/Gearbox/gb_ammo.png create mode 100644 GRAPHICS/Gearbox/gb_box.png create mode 100644 GRAPHICS/Gearbox/gb_circ.png create mode 100644 GRAPHICS/Gearbox/gb_cor.png create mode 100644 GRAPHICS/Gearbox/gb_desc.png create mode 100644 GRAPHICS/Gearbox/gb_hand.png create mode 100644 GRAPHICS/Gearbox/gb_hcir.png create mode 100644 GRAPHICS/Gearbox/gb_nope.png create mode 100644 GRAPHICS/Gearbox/gb_pip.png create mode 100644 GRAPHICS/Gearbox/gb_pntr.png create mode 100644 GRAPHICS/Gearbox/gb_weap.png create mode 100644 GRAPHICS/Gearbox/gb_wpsel.png create mode 100644 SOUNDS/gb_nope.ogg create mode 100644 SOUNDS/gb_tick.ogg create mode 100644 SOUNDS/gb_toggle.ogg create mode 100644 language.gearbox create mode 100644 shaders/mfx_bss_blur.fp create mode 100644 sndinfo.gearbox create mode 100644 zscript/MD5/MD5.zs create mode 100644 zscript/gearbox/EquipmentMenu.zs create mode 100644 zscript/gearbox/activity.zs create mode 100644 zscript/gearbox/changer.zs create mode 100644 zscript/gearbox/custom_weapon_order_storage.zs create mode 100644 zscript/gearbox/display/blocky_view.zs create mode 100644 zscript/gearbox/display/blur.zs create mode 100644 zscript/gearbox/display/caption.zs create mode 100644 zscript/gearbox/display/dim.zs create mode 100644 zscript/gearbox/display/fade_in_out.zs create mode 100644 zscript/gearbox/display/text_view.zs create mode 100644 zscript/gearbox/engine/level.zs create mode 100644 zscript/gearbox/engine/player.zs create mode 100644 zscript/gearbox/engine/weapon_watcher.zs create mode 100644 zscript/gearbox/event_handler.zs create mode 100644 zscript/gearbox/event_processor.zs create mode 100644 zscript/gearbox/font_selector.zs create mode 100644 zscript/gearbox/freezer.zs create mode 100644 zscript/gearbox/gearboxInfo/Readme.gearbox create mode 100644 zscript/gearbox/gearboxInfo/changelog.gearbox create mode 100644 zscript/gearbox/gearboxInfo/copying.gearbox create mode 100644 zscript/gearbox/gearboxInfo/credits.gearbox create mode 100644 zscript/gearbox/input.zs create mode 100644 zscript/gearbox/input_processor.zs create mode 100644 zscript/gearbox/inventory_menu.zs create mode 100644 zscript/gearbox/inventory_user.zs create mode 100644 zscript/gearbox/netevent_processor.zs create mode 100644 zscript/gearbox/options.zs create mode 100644 zscript/gearbox/pb/tokens.zs create mode 100644 zscript/gearbox/printer.zs create mode 100644 zscript/gearbox/sender.zs create mode 100644 zscript/gearbox/service/hide_service.zs create mode 100644 zscript/gearbox/service/icon_service.zs create mode 100644 zscript/gearbox/service/service.zs create mode 100644 zscript/gearbox/sounds.zs create mode 100644 zscript/gearbox/specials_menu.zs create mode 100644 zscript/gearbox/tools/ammo.zs create mode 100644 zscript/gearbox/tools/cvar.zs create mode 100644 zscript/gearbox/tools/log.zs create mode 100644 zscript/gearbox/tools/texture_cache.zs create mode 100644 zscript/gearbox/view_model.zs create mode 100644 zscript/gearbox/weapon_data.zs create mode 100644 zscript/gearbox/weapon_data_loader.zs create mode 100644 zscript/gearbox/weapon_menu.zs create mode 100644 zscript/gearbox/wheel/controller.zs create mode 100644 zscript/gearbox/wheel/controller_model.zs create mode 100644 zscript/gearbox/wheel/indexer.zs create mode 100644 zscript/gearbox/wheel/inner_indexer.zs create mode 100644 zscript/gearbox/wheel/multiwheel.zs create mode 100644 zscript/gearbox/wheel/multiwheel_mode.zs create mode 100644 zscript/gearbox/wheel/screen.zs create mode 100644 zscript/gearbox/wheel/text.zs create mode 100644 zscript/gearbox/wheel/view.zs create mode 100644 zscript/m_gizmos/previous_weapon.zs create mode 100644 zscript/zabor/event_handler.zs diff --git a/CVARINFO b/CVARINFO index 311cdc9709..343362dba9 100644 --- a/CVARINFO +++ b/CVARINFO @@ -520,3 +520,63 @@ server noarchive bool CanGive = false; server noarchive int SMO = 0; server int DynamicSpawnMonsters = 1; server int levelsToPlay = 32; + +//gearbox cvars +// When adding new options, don't forget to add them to: +// - zscript/zabor/event_handler.zs:printGearboxCvars(). +// - keyconf.txt:Alias gb_reset. + +// Options ///////////////////////////////////////////////////////////////////////////////////////// + +user int gb_scale = 1; +user color gb_color = "22 22 CC"; +user color gb_dim_color = "99 99 99"; +user bool gb_show_tags = true; + +// 0 - blocky view +// 1 - wheel view +user int gb_view_type = 1; + +user bool gb_enable_dim = true; +user bool gb_enable_blur = false; +user float gb_wheel_position = 1.0; +user float gb_wheel_scale = 1.0; +user bool gb_wheel_tint = true; +user int gb_multiwheel_limit = 12; + +user float gb_blocks_position_x = 0.0; +user float gb_blocks_position_y = 0.0; + +user int gb_text_scale = 1; +user float gb_text_position_x = 0.0; +user float gb_text_position_y = 0.0; +user float gb_text_position_y_max = 1.0; +user int gb_text_usual_color = 21; // cyan +user int gb_text_selected_color = 9; // white + +user string gb_font = "NewSmallFont"; + +user bool gb_open_on_scroll = false; + +user bool gb_open_on_slot = true; +user bool gb_reverse_slot_cycle_order = false; +user bool gb_select_first_slot_weapon = false; + +user bool gb_mouse_in_wheel = true; +user bool gb_select_on_key_up = false; +user bool gb_no_menu_if_one = false; +user bool gb_on_automap = false; +user bool gb_lock_positions = false; +user bool gb_enable_sounds = true; +user bool gb_frozen_can_open = false; + +user int gb_time_freeze = 0; + +user float gb_mouse_sensitivity_x = 1.0; +user float gb_mouse_sensitivity_y = 1.0; + +user bool gb_zabor_enabled = true; + +// Custom weapon order storage ///////////////////////////////////////////////////////////////////// + +nosave string gb_custom_weapon_order = ""; \ No newline at end of file diff --git a/GLDEFS b/GLDEFS index 7433614e34..36a24c14dc 100644 --- a/GLDEFS +++ b/GLDEFS @@ -21,6 +21,13 @@ #include "models/nashgore/CorpseBlood/_materials.txt" #include "models/nashgore/Footprint/_materials.txt" +HardwareShader postprocess scene +{ + Name "gb_blur" + Shader "shaders/mfx_bss_blur.fp" 330 +} + + pulselight M1HeatWave { color 0.0 0.1 1.0 diff --git a/GRAPHICS/Gearbox/gb_ammo.png b/GRAPHICS/Gearbox/gb_ammo.png new file mode 100644 index 0000000000000000000000000000000000000000..1ff059534c699a104a9bf9c9fac8635477e6473c GIT binary patch literal 81 zcmeAS@N?(olHy`uVBq!ia0vp^8bHj(!2~4jth{T0l!T{?V~EE2;sI@UN eEf(Xi?`Cv6!DM@Te;o%<4TGnvpUXO@geCxiq7xVZ literal 0 HcmV?d00001 diff --git a/GRAPHICS/Gearbox/gb_box.png b/GRAPHICS/Gearbox/gb_box.png new file mode 100644 index 0000000000000000000000000000000000000000..b15da62a6dfcf638258de9458ee7d7fe22cef90f GIT binary patch literal 1127 zcmV-t1ep7YP)P000>X1^@s6#OZ}&0004mX+uL$Nkc;* zaB^>EX>4Tx04R}tkv&MmKpe$iQ^g_`5j%)DWT;Mdu_EGBs#pXIrLEAagUO{|(4-+r zad8w}3l4rPRvlcNb#-tR1i=pwX9p)m7b)?7NufoI2gm(*ckglc4iK7ErkXu5K-DZG zorsIM{HoaZiU5WnU=C4NI`^*Ix48bLWEY06ccIMk9+t>9e;{kGPx>X zDYDGy0}HFnkO2uerUo_i_3FWT~s=8{ps& zm?%;9y2rZ%-M#&LrrqBU5mj>LkNHi>00006VoOIv0001g0RM~Zd!GOR010qNS#tmY zE+YT{E+YYWr9XB6000McNliru=m-%50uDLexu^gD02y>eSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{00KWrL_t(Y$HkV(a??N%hQFRovXQZJ$|Lap@4}ga1BXBX zj_pL&GSeJJ##UC5uuN$yO{qV1Pxs#=eEO*T<>3JMFx;@y$f4^Q4n0W~eF0kpLe==kP16#iqN#KEL0yzmBv`LI==vqMSWq;K2l;s4}^26GKd-bQUgEP*vif zv`yl6r`&u`G~KA1Xa-AINWmeM~Lwis(&RAXx#rT~svz=^n z0?8dM6qagjvs|yT*qv+9qv3L3Tm~%HmQWb1HQAdZ#Fd`Hrm>?bZJS6@kNsd?F&G&; zqoKZBuvi6x^Cw6V9F>EpP+G$f)2|*ge-n{IWUXPb3apj|rLiZ-5=$*g@Sd(5`@twZ tdS8pCxii^FWLR*X(pV~Np7{s+uAi|@5LQ(`#Kiyr002ovPDHLkV1ja^@oE47 literal 0 HcmV?d00001 diff --git a/GRAPHICS/Gearbox/gb_circ.png b/GRAPHICS/Gearbox/gb_circ.png new file mode 100644 index 0000000000000000000000000000000000000000..2ca424f4e574197f74ab0f17ed300fad7c44abad GIT binary patch literal 101919 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Μf&OE5t2~chtmo!#4Or|;{N4?iU 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 literal 0 HcmV?d00001 diff --git a/GRAPHICS/Gearbox/gb_desc.png b/GRAPHICS/Gearbox/gb_desc.png new file mode 100644 index 0000000000000000000000000000000000000000..3cf6cee37565306626b29c07d82b7cb241475c39 GIT binary patch literal 13115 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

HDOZHL;5>PYMD`)a9 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 literal 0 HcmV?d00001 diff --git a/GRAPHICS/Gearbox/gb_hand.png b/GRAPHICS/Gearbox/gb_hand.png new file mode 100644 index 0000000000000000000000000000000000000000..063c5780a6be6c88d8ee09f88f17bab40e29c40f GIT binary patch literal 8053 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

---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 literal 0 HcmV?d00001 diff --git a/GRAPHICS/Gearbox/gb_pntr.png b/GRAPHICS/Gearbox/gb_pntr.png new file mode 100644 index 0000000000000000000000000000000000000000..ca43e7deac48e347fe765518c34964b9bd1f21b4 GIT binary patch literal 5565 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 literal 0 HcmV?d00001 diff --git a/GRAPHICS/Gearbox/gb_wpsel.png b/GRAPHICS/Gearbox/gb_wpsel.png new file mode 100644 index 0000000000000000000000000000000000000000..645b3390a93877869bf41894e57deff6f2f9a52b GIT binary patch literal 1862 zcmV-M2f6r(P)EX>4Tx04R}tkv&MmKpe$iQ^g_`Q8S1*1gTDTQ4z;d#UfZJZG~1HOfLO`CJjl7 zi=*ILaPVWX>fqw6tAnc`2!4P#J2)x2NQwVT3N2ziIPS;0dyl(!fKV?p&FYE(nr@rP zSX9bnSEZg;1TX**V;Gc~WyDj9X>=T4_we!cF3ht!pZjz4sX2=QK8ZNO4AUmwAfDZ{ z4bJ<-A}h)&@j3CNNf#u3*o ztDLtuYvn3y-jlyDnA2BMT&FpT7?u!60umHdQ9>Cu!nEq7SV+=-w1a=d^(V-skgEhn zjs;YpL3aJ%fAG6oD?dHuB?Y5E_lx6vi~~KpK%?e3-^Y&AI05|6z?I(eSL(prC+W48 z7CHj@w}Ff6mZt0hmpj1llOdb3EBR>(`8@D`M&FbLhHio0HLthkK29HiGeSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{00kaNL_t(|+U=a}avC=jhMyy87el~?#!dQ_>5uf{auvOh zUR>&^Fkk(5qLV-vh!#9)&gg#}EL=XTxL9c0aN~N!;IKwkTb%qjh zo$ka8NDrZp(PNAVq9-)!33LU~fNG8EC4n=%{)gu*dWeq)&9aYdWxZJ47Stk)2m($g zc&Q?Z=z0i1i2cS4p@w-UDTyBl{w#YT7~ zzakZ6zQn=fsqkFJRVROEWbMo*wCl2z3h)tLNfXJ)`XZ6FjmZPgYjbUFX_D94!ByBG zFwMxy$jZq2tdJ!XIC}K)Ot3UGoT5DA&Q%hh%$AFjm*3Pep9(0V`?u-r2AIXNj%5?i z^9K|#+87)TFifxlwmh3;oNU@)u+B3GGaWMyP!WF0cHECY^ECY1b4 z%@|L`e6{wz?x1R$N1OJa0H?qSaKv)ag>*2~mNif%_-a3El95==&DH%rd0i#vwC{(` z|5!$C+x@;0p@(pcVN5}fK4v+|k>1jF#WiP4x!_M`{E?CMNs;vj{SMRwK);O0UOAz*%w;l{{vj2Uvo_f!-#{DM-U zi=5CQ?&z@V9?*W|`k|8_yAQJBmKre^H%HcE979SRN2^9zXI%1@*IdZ&%=9Imb3z;{ zexA$N_O+4DrnOTbIN&jcAFw>9$CweGaqCO2(;|dozS96B0v1CR?;3^wC+mCYV(C4- zu0$Bbtu};S@X{>iv%W4t{6_@ZmJ}gQ*n$wS;weOUB*GA7ybwebOo=nc^IbXgghr~k z14rJVX}6e^h$)B2fI>#}q{K@5qn^*?r$7h&V(Fj4iN66J%Kr z`Yi)vAjlp^Ctth8?fr~`^NAoY0%(=%=MbFd18-bLv;NeGSpWb407*qoM6N<$f(!g# AHvj+t literal 0 HcmV?d00001 diff --git a/KEYCONF.txt b/KEYCONF.txt index 7d16ea0297..07e001cd62 100644 --- a/KEYCONF.txt +++ b/KEYCONF.txt @@ -3,13 +3,21 @@ addplayerclass PB_PlayerPawn addkeysection "Project Brutality" PBAdvMoves - AddMenuKey "Weapon Special Wheel" "+PBWeaponSpecial" + Alias +pb_specialwheel "event pb_special_wheel" + Alias -pb_specialwheel "event pb_special_wheel_up" + Alias +pb_equipmenu "event pb_equip_wheel" + Alias -pb_equipmenu "event pb_equip_wheel_up" + + AddMenuKey "$PB_TOGGLE_SPECIALS_MENU" +pb_specialwheel + AddMenuKey "$PB_TOGGLE_EQUIP_MENU" +pb_equipmenu + + /*AddMenuKey "Weapon Special Wheel" "+PBWeaponSpecial" alias "+PBWeaponSpecial" "netevent PBWeaponSpecialOn" alias "-PBWeaponSpecial" "netevent PBWeaponSpecialOff" AddMenuKey "Equipment Selection Wheel" "+PBEquipSpecial" alias "+PBEquipSpecial" "netevent PBEquipSpecialOn" - alias "-PBEquipSpecial" "netevent PBEquipSpecialOff" + alias "-PBEquipSpecial" "netevent PBEquipSpecialOff"*/ addmenukey "Unload Gun" unreloader alias unreloader "puke 363" @@ -53,3 +61,35 @@ defaultbind L "netevent EV_ClearGore" alias pb_minimal_hud_style "set pb_hudxmargin -9; set pb_hudymargin -9; set pb_hudalpha 0.2; set pb_hudboxalpha 0; set pb_hideunusedtypes true; set pb_curmaxammolist false; set pb_showlevelstats false; set pb_showhudvisor false; set pb_showhudvisorglass false; set pb_huddynamics false; set pb_showammolist false" alias pb_full_hud_style "set pb_hudxmargin 0; set pb_hudymargin 0; set pb_hudalpha 1; set pb_hudboxalpha 1; set pb_hideunusedtypes true; set pb_curmaxammolist true; set pb_showlevelstats false; set pb_showhudvisor true; set pb_showhudvisorglass true; set pb_huddynamics true; set pb_showammolist true" + +//gearbox +// Aliases ///////////////////////////////////////////////////////////////////////////////////////// + +Alias +gb_toggle_weapon_menu "event gb_toggle_weapon_menu" +Alias -gb_toggle_weapon_menu "event gb_toggle_weapon_menu_up" + +Alias +gb_toggle_inventory_menu "event gb_toggle_inventory_menu" +Alias -gb_toggle_inventory_menu "event gb_toggle_inventory_menu_up" + +Alias gb_prev_weapon "netevent gb_prev_weapon" + +Alias gb_rotate_weapon_priority "event gb_rotate_weapon_priority" +Alias gb_rotate_weapon_slot "event gb_rotate_weapon_slot" +Alias gb_reset_custom_order "netevent gb_reset_custom_order" + + +Alias zabor "event zabor" + +Alias gb_copy_player_color "gb_color $color" + +Alias gb_reset "ResetCvar gb_scale; ResetCvar gb_color; ResetCvar gb_dim_color; ResetCvar gb_show_tags; ResetCvar gb_view_type; ResetCvar gb_enable_dim; ResetCvar gb_enable_blur; ResetCvar gb_wheel_position; ResetCvar gb_wheel_scale; ResetCvar gb_wheel_tint; ResetCvar gb_multiwheel_limit; ResetCvar gb_blocks_position_x; ResetCvar gb_blocks_position_y; ResetCvar gb_text_scale; ResetCvar gb_text_position_x; ResetCvar gb_text_position_y; ResetCvar gb_text_position_y_max; ResetCvar gb_text_usual_color; ResetCvar gb_text_selected_color; ResetCvar gb_open_on_scroll; ResetCvar gb_open_on_slot; ResetCvar gb_reverse_slot_cycle_order; ResetCvar gb_select_first_slot_weapon; ResetCvar gb_mouse_in_wheel; ResetCvar gb_select_on_key_up; ResetCvar gb_no_menu_if_one; ResetCvar gb_on_automap; ResetCvar gb_lock_positions; ResetCvar gb_enable_sounds; ResetCvar gb_frozen_can_open; ResetCvar gb_time_freeze; ResetCvar gb_mouse_sensitivity_x; ResetCvar gb_mouse_sensitivity_y; ResetCvar gb_zabor_enabled" + +// Keys //////////////////////////////////////////////////////////////////////////////////////////// + +AddKeySection "$GB_KEYSECTION" gb_Keys + +AddMenuKey "$GB_TOGGLE_WEAPON_MENU" +gb_toggle_weapon_menu +AddMenuKey "$GB_TOGGLE_INVENTORY_MENU" +gb_toggle_inventory_menu +AddMenuKey "$GB_PREV_WEAPON" gb_prev_weapon +AddMenuKey "$GB_ROTATE_WEAPON_PRIORITY" gb_rotate_weapon_priority +AddMenuKey "$GB_ROTATE_WEAPON_SLOT" gb_rotate_weapon_slot diff --git a/MENUDEF.txt b/MENUDEF.txt index 55f8d284c1..16acc959c2 100644 --- a/MENUDEF.txt +++ b/MENUDEF.txt @@ -122,6 +122,8 @@ OptionMenu "PBSettings" Submenu "Visual Settings", "RenderingSettings" StaticText " " Submenu "HUD Settings", "PB_HUDOptions" + statictext "" + Submenu "$GB_OPTIONS","gb_Options" StaticText " " StaticText " " StaticText " " @@ -272,7 +274,7 @@ OptionMenu "GameplaySettings" StaticText " " StaticText " " - Option "Weapon Wheel Freezes Time", "py_weaponwheel_freeze", "OnOff" + //Option "Weapon Wheel Freezes Time", "py_weaponwheel_freeze", "OnOff" Option "Weapon ADS Controls","pb_toggle_aim_hold", "AimingStyle" //Looks like we are using this newer line. StaticText " " StaticText "--------Grenade Bounce/Explosion Behavior" @@ -890,3 +892,192 @@ OptionValue "NashGoreGibTypes" 1, "$NASHGOREMNU_GIB_TYPE_NOSTICKYGIBS" 2, "$NASHGOREMNU_VANILLA" } + +// +// Gearbox +// + +// Menus /////////////////////////////////////////////////////////////////////////////////////////// + +OptionMenu gb_Options +{ + + Title "$GB_OPTIONS" + + StaticText "" + Option "$GB_VIEW_TYPE", gb_view_type, gb_ViewTypeValues + + StaticText "" + StaticText "$GB_KEYSECTION", 1 + Control "$GB_TOGGLE_WEAPON_MENU" , "+gb_toggle_weapon_menu" + Control "$GB_TOGGLE_INVENTORY_MENU" , "+gb_toggle_inventory_menu" + Control "$GB_PREV_WEAPON" , "gb_prev_weapon" + Control "$GB_ROTATE_WEAPON_PRIORITY", "gb_rotate_weapon_priority" + Control "$GB_ROTATE_WEAPON_SLOT" , "gb_rotate_weapon_slot" + + StaticText "" + Submenu "$GB_BEHAVIOR_OPTIONS" , gb_BehaviorOptions + Submenu "$GB_UI_OPTIONS" , gb_UiOptions + Submenu "$GB_WHEEL_OPTIONS" , gb_WheelOptions + Submenu "$GB_BLOCKS_OPTIONS" , gb_BlocksOptions + Submenu "$GB_TEXT_OPTIONS" , gb_TextOptions + Submenu "$GB_ADVANCED_OPTIONS" , gb_AdvancedOptions + Submenu "$GB_GZDOOM_OPTIONS" , gb_GZDoomOptions + + StaticText "" + SafeCommand "$GB_RESET" , gb_reset , "$GB_RESET_PROMPT" + SafeCommand "$GB_RESET_CUSTOM_ORDER" , gb_reset_custom_order , "$GB_RESET_CUSTOM_ORDER_PROMPT" + StaticText "$GB_RESET_CUSTOM_ORDER_NOTE", black + +} + +OptionMenu gb_BehaviorOptions +{ + + Title "$GB_BEHAVIOR_OPTIONS_TITLE" + + Option "$GB_OPEN_ON_PREV_NEXT" , gb_open_on_scroll , OnOff + + StaticText "" + Option "$GB_OPEN_ON_SLOT" , gb_open_on_slot , OnOff + Option "$GB_REVERSE_SLOT_CYCLE_ORDER" , gb_reverse_slot_cycle_order , OnOff + Option "$GB_SELECT_FIRST_SLOT_WEAPON" , gb_select_first_slot_weapon , OnOff + Option "$GB_NO_MENU_IF_ONE" , gb_no_menu_if_one , OnOff + + StaticText "" + Option "$GB_SELECT_ON_KEY_UP" , gb_select_on_key_up , OnOff + Option "$GB_FREEZE_TIME" , gb_time_freeze , gb_FreezeValues + Option "$GB_ENABLE_ON_AUTOMAP" , gb_on_automap , OnOff + Option "$GB_LOCK_POSITION" , gb_lock_positions , OnOff + Option "$GB_FROZEN_CAN_OPEN" , gb_frozen_can_open , OnOff + +} + +OptionMenu gb_GZDoomOptions +{ + + Title "$GB_GZDOOM_OPTIONS_TITLE" + + StaticText "" + Option "$SCALEMNU_HUDASPECT", hud_AspectScale, OnOff + StaticText "$GB_ASPECT_SCALE_NOTE", black + +} + +OptionMenu gb_UiOptions +{ + + Title "$GB_UI_OPTIONS_TITLE" + + StaticText "" + ColorPicker "$GB_COLOR" , gb_color + Command "$GB_PLAYER_COLOR", gb_copy_player_color + + StaticText "" + Option "$GB_SHOW_TAGS" , gb_show_tags , OnOff + + StaticText "" + Option "$GB_DIM" , gb_enable_dim , OnOff + ColorPicker "$GB_DIM_COLOR" , gb_dim_color + Option "$GB_BLUR" , gb_enable_blur , OnOff + + StaticText "" + Option "$GB_SOUND" , gb_enable_sounds , OnOff + + StaticText "" + Option "$GB_FONT" , gb_font , gb_FontValues + TextField "$GB_FONT_CUSTOM" , gb_font +} + +OptionMenu gb_WheelOptions +{ + + Title "$GB_WHEEL_OPTIONS_TITLE" + + StaticText "" + Slider "$GB_WHEEL_X" , gb_wheel_position , -1.5, 1.5, 0.1, 1 + Slider "$GB_WHEEL_SCALE" , gb_wheel_scale , 0.1, 2, 0.1, 1 + Option "$GB_WHEEL_TINT" , gb_wheel_tint , OnOff + + StaticText "" + Option "$GB_MOUSE_IN_WHEEL" , gb_mouse_in_wheel , OnOff + Slider "$GB_MULTIWHEEL_LIMIT" , gb_multiwheel_limit , 3, 100, 1, 0 + + StaticText "" + StaticText "$GB_MOUSE_SENSITIVITY", 1 + Slider "$GB_X", gb_mouse_sensitivity_x, 0.1, 5, 0.1, 1 + Slider "$GB_Y", gb_mouse_sensitivity_y, 0.1, 5, 0.1, 1 + +} + +OptionMenu gb_BlocksOptions +{ + + Title "$GB_BLOCKS_OPTIONS_TITLE" + + StaticText "" + Slider "$GB_SCALE" , gb_scale, 1, 8, 1, 0 + + StaticText "" + StaticText "$GB_POSITION", 1 + Slider "$GB_X" , gb_blocks_position_x, 0.0, 1.0, 0.01, 2 + Slider "$GB_Y" , gb_blocks_position_y, 0.0, 1.0, 0.01, 2 + +} + +OptionMenu gb_TextOptions +{ + + Title "$GB_TEXT_OPTIONS_TITLE" + + StaticText "" + Slider "$GB_SCALE" , gb_text_scale, 1, 8, 1, 0 + + StaticText "" + StaticText "$GB_POSITION", 1 + Slider "$GB_X" , gb_text_position_x , 0.0, 1.0, 0.01, 2 + Slider "$GB_Y" , gb_text_position_y , 0.0, 1.0, 0.01, 2 + Slider "$GB_Y_BOUNDARY" , gb_text_position_y_max , 0.0, 1.0, 0.01, 2 + + StaticText "" + Option "$GB_TEXT_USUAL_COLOR" , gb_text_usual_color , TextColors + Option "$GB_TEXT_SELECTED_COLOR" , gb_text_selected_color , TextColors + +} + +OptionMenu gb_AdvancedOptions +{ + + Title "$GB_ADVANCED_OPTIONS_TITLE" + + StaticText "" + Option "$GB_VM_ABORT_INFO_ENABLED", gb_zabor_enabled, OnOff + +} + +// Option Values /////////////////////////////////////////////////////////////////////////////////// + +OptionValue gb_ViewTypeValues +{ + 0, "$GB_BLOCKY_VIEW" + 1, "$GB_WHEEL_VIEW" + 2, "$GB_TEXT_VIEW" +} + +OptionValue gb_FreezeValues +{ + 0, "$OPTVAL_OFF" + 1, "$GB_LEVEL_AND_PLAYER" + 2, "$GB_PLAYER" +} + +OptionString gb_FontValues +{ + "NewSmallFont" , "$GB_NEW_SMALL_FONT" + "SmallFont" , "$GB_OLD_SMALL_FONT" + "ConsoleFont" , "$GB_CONSOLE_FONT" + "BigFont" , "$GB_BIG_FONT" + "PBFONT" , "$PB_FONT" + "PBBOLD" , "$PB_BOLDFONT" + "LOWQFONT" , "$PB_LOWQFONT" +} diff --git a/SOUNDS/gb_nope.ogg b/SOUNDS/gb_nope.ogg new file mode 100644 index 0000000000000000000000000000000000000000..0740f562020c3363a40c02a94ee49260aa23d613 GIT binary patch literal 5253 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 literal 0 HcmV?d00001 diff --git a/SOUNDS/gb_toggle.ogg b/SOUNDS/gb_toggle.ogg new file mode 100644 index 0000000000000000000000000000000000000000..02f8172d6748dc49eb40ecb72812477a0b1433fb GIT binary patch literal 22419 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 literal 0 HcmV?d00001 diff --git a/ZSCRIPT.zc b/ZSCRIPT.zc index bb027e7426..11462ba8f3 100644 --- a/ZSCRIPT.zc +++ b/ZSCRIPT.zc @@ -25,9 +25,90 @@ const MAXITERATIONS = 32676; #include "zscript/Weapons/executionGUI/pb_uihack.zs" #include "zscript/Weapons/executionGUI/pb_execution_handler.zs" #include "zscript/ZMoveMenu.ZMV" -#include "zscript/PbWheel/parse.zsc" -#include "zscript/PbWheel/ev_core_special.zsc" -#include "zscript/PbWheel/ev_drawer_special.zsc" +//#include "zscript/PbWheel/parse.zsc" +//#include "zscript/PbWheel/ev_core_special.zsc" +//#include "zscript/PbWheel/ev_drawer_special.zsc" + +//////////////////////////////////////////////////////// +// Gearbox includes +/////////////////////////////////////////////////////// +#include "zscript/gearbox/weapon_data.zs" +#include "zscript/gearbox/weapon_data_loader.zs" +#include "zscript/gearbox/printer.zs" + +#include "zscript/MD5/MD5.zs" +#include "zscript/gearbox/custom_weapon_order_storage.zs" + +#include "zscript/gearbox/activity.zs" +#include "zscript/gearbox/input.zs" +#include "zscript/gearbox/input_processor.zs" +#include "zscript/gearbox/event_processor.zs" +#include "zscript/gearbox/sounds.zs" + +#include "zscript/gearbox/weapon_menu.zs" +#include "zscript/gearbox/inventory_menu.zs" + +//pb things +#include "zscript/gearbox/specials_menu.zs" +#include "zscript/gearbox/EquipmentMenu.zs" +#include "zscript/gearbox/pb/tokens.zs" //this is were specials and equipments are defined to be used by the handler + +#include "zscript/gearbox/inventory_user.zs" + +#include "zscript/gearbox/sender.zs" + +#include "zscript/gearbox/netevent_processor.zs" +#include "zscript/gearbox/changer.zs" + +#include "zscript/gearbox/view_model.zs" + +// Displaying data on screen. +#include "zscript/gearbox/display/dim.zs" +#include "zscript/gearbox/display/blur.zs" +#include "zscript/gearbox/display/blocky_view.zs" +#include "zscript/gearbox/display/text_view.zs" +#include "zscript/gearbox/display/fade_in_out.zs" +#include "zscript/gearbox/display/caption.zs" + +#include "zscript/gearbox/options.zs" +#include "zscript/gearbox/font_selector.zs" + +// Weapon Wheel implementation. +#include "zscript/gearbox/wheel/view.zs" +#include "zscript/gearbox/wheel/controller.zs" +#include "zscript/gearbox/wheel/controller_model.zs" +#include "zscript/gearbox/wheel/inner_indexer.zs" +#include "zscript/gearbox/wheel/indexer.zs" +#include "zscript/gearbox/wheel/multiwheel.zs" +#include "zscript/gearbox/wheel/multiwheel_mode.zs" +#include "zscript/gearbox/wheel/text.zs" +#include "zscript/gearbox/wheel/screen.zs" + +// Utility tools. +#include "zscript/gearbox/tools/cvar.zs" +#include "zscript/gearbox/tools/log.zs" +#include "zscript/gearbox/tools/texture_cache.zs" +#include "zscript/gearbox/tools/ammo.zs" + +// Helper classes gearbox/that wrap access to game information provided by the engine. +#include "zscript/gearbox/engine/level.zs" +#include "zscript/gearbox/engine/weapon_watcher.zs" +#include "zscript/gearbox/engine/player.zs" + +#include "zscript/gearbox/freezer.zs" + +#include "zscript/gearbox/event_handler.zs" + +#include "zscript/gearbox/service/service.zs" +#include "zscript/gearbox/service/icon_service.zs" +#include "zscript/gearbox/service/hide_service.zs" + +// Libraries +#include "zscript/m_gizmos/previous_weapon.zs" +#include "zscript/zabor/event_handler.zs" +// +// end of gearbox includes +// //Spices and Seasoning #include "zscript/TiltPlus/TiltPlusPlus.zc" diff --git a/language.gearbox b/language.gearbox new file mode 100644 index 0000000000..e7d182eab4 --- /dev/null +++ b/language.gearbox @@ -0,0 +1,102 @@ +[enu default] + +GB_OPTIONS = "\ch⚙\c- Gearbox Options"; + +GB_UI_OPTIONS = "UI options"; +GB_UI_OPTIONS_TITLE = "Gearbox UI Options"; + +GB_COLOR = "Color"; +GB_PLAYER_COLOR = "Copy custom player color"; +GB_DIM_COLOR = "Background dimming color"; + +GB_SHOW_TAGS = "Tags"; +GB_SOUND = "Sound effects"; + +GB_KEYSECTION = "Gearbox Keys"; +GB_TOGGLE_WEAPON_MENU = "Toggle weapon menu"; +GB_TOGGLE_INVENTORY_MENU = "Toggle inventory menu"; +GB_PREV_WEAPON = "Select previous weapon"; +PB_TOGGLE_SPECIALS_MENU = "Activate weapon special"; +PB_TOGGLE_EQUIP_MENU = "Open equipments wheel"; + +GB_VIEW_TYPE = "Selector type"; +GB_BLOCKY_VIEW = "Blocks"; +GB_WHEEL_VIEW = "Wheel"; +GB_TEXT_VIEW = "Plain text"; +GB_DIM = "Background dimming"; +GB_BLUR = "Background blur"; +GB_WHEEL_X = "Horizontal position"; +GB_WHEEL_SCALE = "Scale"; +GB_WHEEL_TINT = "Colored tint on weapons"; + +GB_BEHAVIOR_OPTIONS = "Behavior options"; +GB_BEHAVIOR_OPTIONS_TITLE = "Gearbox Behavior Options"; + +GB_OPEN_ON_PREV_NEXT = "Intercept next/previous weapon keys"; +GB_OPEN_ON_SLOT = "Intercept slot keys"; +GB_SELECT_ON_KEY_UP = "Select on Toggle Menu key release"; +GB_NO_MENU_IF_ONE = "Slot keys select if weapon is only one in slot"; +GB_ENABLE_ON_AUTOMAP = "Gearbox on automap"; +GB_LOCK_POSITION = "Locked positions"; +GB_FROZEN_CAN_OPEN = "Frozen player can open Gearbox"; + +GB_REVERSE_SLOT_CYCLE_ORDER = "Reverse cycling order for the slot key"; +GB_SELECT_FIRST_SLOT_WEAPON = "Always start at first weapon for the slot key"; + +GB_FREEZE_TIME = "Freeze"; +GB_LEVEL_AND_PLAYER = "Enemies and player (single-player only)"; +GB_PLAYER = "Player"; + +GB_MOUSE_IN_WHEEL = "Enable mouse input"; +GB_MULTIWHEEL_LIMIT = "Multiwheel threshold"; + +GB_MOUSE_SENSITIVITY = "Mouse sensitivity"; +GB_X = "Horizontal"; +GB_Y = "Vertical"; +GB_Y_BOUNDARY = "Vertical lower boundary"; + +GB_WHEEL_OPTIONS = "Wheel options"; +GB_WHEEL_OPTIONS_TITLE = "Gearbox Wheel Options"; +GB_BLOCKS_OPTIONS = "Blocks options"; +GB_BLOCKS_OPTIONS_TITLE = "Gearbox Blocks Options"; + +GB_TEXT_OPTIONS = "Plain text options"; +GB_TEXT_OPTIONS_TITLE = "Gearbox Plain Text Options"; +GB_TEXT_USUAL_COLOR = "Usual color"; +GB_TEXT_SELECTED_COLOR = "Selected color"; + +GB_FONT = "Font"; +GB_FONT_CUSTOM = "Font (custom)"; +GB_BAD_FONT = "Font \"%s\" not found."; + +GB_SCALE = "Scale"; +GB_POSITION = "Position"; +GB_X = "Horizontal"; +GB_Y = "Vertical"; + +GB_RESET = "Reset Gearbox options to defaults"; +GB_RESET_PROMPT = "Do you really want to reset Gearbox options to defaults?"; + +GB_RESET_CUSTOM_ORDER = "Reset current custom weapon order"; +GB_RESET_CUSTOM_ORDER_PROMPT = "Do you really want to reset custom weapon order?"; +GB_RESET_CUSTOM_ORDER_NOTE = "Works only while in game."; + +GB_GZDOOM_OPTIONS = "Relevant GZDoom options"; +GB_GZDOOM_OPTIONS_TITLE = "GZDoom Options that affect Gearbox"; +GB_ASPECT_SCALE_NOTE = "Doesn't affect weapons in wheel for now."; + +GB_ADVANCED_OPTIONS = "Advanced options"; +GB_ADVANCED_OPTIONS_TITLE = "Gearbox Advanced Options"; +GB_VM_ABORT_INFO_ENABLED = "VM abort info"; + +GB_ROTATE_WEAPON_PRIORITY = "Cycle weapon priority"; +GB_ROTATE_WEAPON_SLOT = "Cycle weapon slot"; + +GB_NEW_SMALL_FONT = "New small font"; +GB_OLD_SMALL_FONT = "Old small font"; +GB_CONSOLE_FONT = "Console font"; +GB_BIG_FONT = "Big font"; + +PB_FONT = "PB Font"; +PB_BOLDFONT = "PB Bold Font"; +PB_LOWQFONT = "PB Hud low res font"; diff --git a/shaders/mfx_bss_blur.fp b/shaders/mfx_bss_blur.fp new file mode 100644 index 0000000000..491708920f --- /dev/null +++ b/shaders/mfx_bss_blur.fp @@ -0,0 +1,31 @@ +/* + BlurSharpShift blur from MariENB + (C)2012-2019 Marisa Kirisame + + Modified by m8f 2021 (made radius a constant). +*/ +void main() +{ + vec2 coord = TexCoord; + vec4 res = texture(InputTexture,coord); + vec2 ofs[16] = vec2[] + ( + vec2(1.0,1.0), vec2(-1.0,-1.0), + vec2(-1.0,1.0), vec2(1.0,-1.0), + + vec2(1.0,0.0), vec2(-1.0,0.0), + vec2(0.0,1.0), vec2(0.0,-1.0), + + vec2(1.41,0.0), vec2(-1.41,0.0), + vec2(0.0,1.41), vec2(0.0,-1.41), + + vec2(1.41,1.41), vec2(-1.41,-1.41), + vec2(-1.41,1.41), vec2(1.41,-1.41) + ); + vec2 bresl = vec2(textureSize(InputTexture,0)); + float radius = 2.0; + vec2 bof = radius/bresl; + for ( int i=0; i<16; i++ ) res.rgb += texture(InputTexture,coord+ofs[i]*bof).rgb; + res.rgb /= 17.0; + FragColor = res; +} diff --git a/sndinfo.gearbox b/sndinfo.gearbox new file mode 100644 index 0000000000..e2efd2e6c9 --- /dev/null +++ b/sndinfo.gearbox @@ -0,0 +1,11 @@ +gearbox/tick sounds/gb_tick.ogg +$volume gearbox/tick 0.5 + +gearbox/open sounds/gb_toggle.ogg +$volume gearbox/open 0.2 + +gearbox/close sounds/gb_toggle.ogg +$volume gearbox/close 0.2 + +gearbox/nope sounds/gb_nope.ogg +$volume gearbox/nope 0.5 diff --git a/zmapinfo.txt b/zmapinfo.txt index 84a8977882..b48c054e4d 100644 --- a/zmapinfo.txt +++ b/zmapinfo.txt @@ -108,7 +108,9 @@ GameInfo DimColor = "Black" DimAmount = 0.70 NoRandomPlayerClass = True - AddEventHandlers = "PB_ExecutionHandler", "PB_HudFXHandler", "PB_EventHandler", "MBlurHandler", "NashGoreHandler", "TiltPlusPlusHandler", "DEDashJumpHandler", "SpeedoMeterHandler", "WallSlideHandler", "PB_SpecialWheelHandler" + AddEventHandlers = "PB_ExecutionHandler", "PB_HudFXHandler", "PB_EventHandler", "MBlurHandler", "NashGoreHandler", "TiltPlusPlusHandler", "DEDashJumpHandler", "SpeedoMeterHandler", "WallSlideHandler", //"PB_SpecialWheelHandler" + "gb_EventHandler", "gb_PreviousWeaponEventHandler", "gb_PreviousWeaponStorage", "gb_VmAbortHandler" + NormForwardMove = 0x32, 0x32 //Walk speed is modulated through NormSideMove = 0x32, 0x32 //CVar in the menu options AddEventHandlers = "UnmakerStuff" diff --git a/zscript/MD5/MD5.zs b/zscript/MD5/MD5.zs new file mode 100644 index 0000000000..89bedfe5f6 --- /dev/null +++ b/zscript/MD5/MD5.zs @@ -0,0 +1,196 @@ +// Copyright 2020 3saster +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. The name of the author may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +//=========================================================================== +// +// MD5 Implementation in ZScript +// by 3saster +// +// A class that returns a string containing the MD5 of an input string, using +// the Hash method. In particular, this is designed to hash the result of the +// output of Wads.ReadLump. The "trunc" variable ignores the last "letter" of +// the input before hashing; this is because ReadLump adds a null character +// to the array. If you are not hashing the result of ReadLump, you will +// likely want to set that variable to false. +// +// You are welcome to to use this in your mods, no need to ask for permission, +// as long as the above copyright notice is included, and credit is given. +// +//=========================================================================== + +Class gb_MD5 +{ + // Some functions needed for MD5 + protected static uint F (uint B, uint C, uint D) { return (B & C) | (~B & D); } + protected static uint G (uint B, uint C, uint D) { return (B & D) | (C & ~D); } + protected static uint H (uint B, uint C, uint D) { return B ^ C ^ D; } + protected static uint I (uint B, uint C, uint D) { return C ^ (B | ~D); } + protected static uint leftrotate (uint x, uint c) { return (x << c) | (x >> (32-c)); } + // Swap endianess of byte bitwise + protected static uint swapByte (uint x) + { + uint r = 0; + for(int i=0; i<8; i++) + r += ((x>>i) & 1) << (7-i); + return r; + } + // Swap endianess of word bytewise + protected static uint swapWord (uint x) + { + uint r = 0; + for(int i=0; i<32; i += 8) + r += ((x>>i) & 0xFF) << (24-i); + return r; + } + + static string Hash(string key, bool trunc = true) + { + // Cut the last letter off, since readLump adds a null character to the string, + // causing the hash obtained to not match the actual hash of the file + if(trunc) + key.Truncate(key.length() - 1); + + // s specifies the per-round shift amounts + uint s[64] = { 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, + 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, + 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, + 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 }; + + // Constants + uint K[64] = { 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, + 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, + 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, + 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, + 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, + 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 }; + + // Initialize variables: + uint a0 = 0x67452301; // A + uint b0 = 0xefcdab89; // B + uint c0 = 0x98badcfe; // C + uint d0 = 0x10325476; // D + + // Turn string to byte array + Array input; + for(uint i=0; i < key.length(); i++) + { + input.push(key.ByteAt(i)); + } + + // Padding - add a single 1 bit, then add zeros until the number of bytes is 56 mod 64 + input.push(0x80); + while( input.size()%64 != 56 ) + { + input.push(0x00); + } + + // Pad the remaining 8 bytes with the original message length in little endian + uint strLen = 8*key.length(); + for(uint i=0; i<8; i++) + { + input.push(strLen & 0xFF); + strLen >>= 8; + } + + // Swap endianess of each byte + for(uint i=0; i < input.size(); i++) + { + input[i] = swapByte(input[i]); + } + + // Break into 64 byte chunks + for(uint front=0; front < input.size(); front += 64) + { + //break chunk into 16 four-byte words + Array M; + for(int k=0; k<64; k += 4) + { + M.push( + ( swapByte(input[front + k + 0]) << 0 ) + + ( swapByte(input[front + k + 1]) << 8 ) + + ( swapByte(input[front + k + 2]) << 16) + + ( swapByte(input[front + k + 3]) << 24) + ); + } + // Initialize hash value for this chunk: + uint A = a0; + uint B = b0; + uint C = c0; + uint D = d0; + + uint f; + uint g; + + // Main Loop + for(uint i=0; i<64; i++) + { + if(0 <= i && i <= 15) + { + f = F(B,C,D); + g = i; + } + else if(16 <= i && i <= 31) + { + f = G(B,C,D); + g = (5*i+1)%16; + } + else if(32 <= i && i <= 47) + { + f = H(B,C,D); + g = (3*i+5)%16; + } + else //if(48 <= i && i <= 63) + { + f = I(B,C,D); + g = (7*i)%16; + } + + f = f + A + K[i] + M[g]; + A = D; + D = C; + C = B; + B = B + leftrotate(f, s[i]); + } + // Add this chunk's hash to result so far: + a0 += A; + b0 += B; + c0 += C; + d0 += D; + } + + return string.format("%08x%08x%08x%08x",swapWord(a0),swapWord(b0),swapWord(c0),swapWord(d0)); + } +} \ No newline at end of file diff --git a/zscript/gearbox/EquipmentMenu.zs b/zscript/gearbox/EquipmentMenu.zs new file mode 100644 index 0000000000..a327fa6edf --- /dev/null +++ b/zscript/gearbox/EquipmentMenu.zs @@ -0,0 +1,111 @@ +Class gb_equipmentmenu +{ + static gb_equipmentmenu from() + { + let nc = new("gb_equipmentmenu"); + nc.mSelectedIndex = 0; + nc.Load(); //load its definitions + return nc; + } + + //this shouldnt be needed, but anyways + bool noequipments() + { + return getEquipmentNumber() == 0; + } + + int getEquipmentNumber() + { + return tags.size(); + } + + ui bool selectNext() + { + int nItems = getEquipmentNumber(); + if (nItems == 0) return false; + + mSelectedIndex = (mSelectedIndex + 1) % nItems; + + return true; + } + + ui bool selectPrev() + { + int nItems = getEquipmentNumber(); + if (nItems == 0) return false; + + mSelectedIndex = (mSelectedIndex - 1 + nItems) % nItems; + + return true; + } + + ui bool setSelectedIndex(int index) + { + if (index == -1 || mSelectedIndex == index) return false; + + int nItems = getEquipmentNumber(); + if(nItems == 0) + return false; + index = clamp(index,0,nItems); + + + mSelectedIndex = index; + + return true; + } + + ui int getSelectedIndex() const + { + return mSelectedIndex; + } + + string ConfirmSelection() const + { + if(token.size() > 0) + return token[mSelectedIndex]; + return ""; + } + + + //just in case anything is added in the future, is automatically handled here + //this will only be called once, when the handler is initialized + //not sure if this may be too heavy + private void Load() + { + for(int i = 0; i < AllClasses.size(); i++) + { + if(AllClasses[i] is "equipmentCard") + { + let eq = equipmentCard(new(AllClasses[i])); + if(eq) + eq.InfoFiller(tags,token,img,scalex,scaley); + } + } + } + + ui void fill(out gb_ViewModel viewModel) + { + for(int i = 0; i < tags.size(); i++) + { + viewModel.tags .push(tags[i]); + viewModel.slots .push(i + 1); + viewModel.indices .push(i); + viewModel.icons .push(texman.checkfortexture(img[i])); + viewModel.iconScaleXs .push(scalex[i]); + viewModel.iconScaleYs .push(scaley[i]); + viewModel.quantity1 .push(-1); //no ammount for you >:( + viewModel.maxQuantity1.push(-1); // + viewModel.quantity2 .push(-1); // + viewModel.maxQuantity2.push(-1); // + } + + viewModel.selectedIndex = clamp(mSelectedIndex,0,tags.size()-1); //without this, blocks/text wont work + } + + array tags; + array token; + array img; + array scalex; + array scaley; + private int mSelectedIndex; +} diff --git a/zscript/gearbox/activity.zs b/zscript/gearbox/activity.zs new file mode 100644 index 0000000000..54117f1331 --- /dev/null +++ b/zscript/gearbox/activity.zs @@ -0,0 +1,72 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2020-2021 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +/** + * This class stores top-level Gearbox state. It can be either Weapons, + * Inventory, or None. + */ +class gb_Activity +{ + + static + gb_Activity from() + { + let result = new("gb_Activity"); + result.mActivity = gb_Activity.None; + return result; + } + + bool isNone() const { return mActivity == gb_Activity.None; } + bool isWeapons() const { return mActivity == gb_Activity.Weapons; } + bool isInventory() const { return mActivity == gb_Activity.Inventory; } + bool isSpecials() const { return mActivity == gb_Activity.Specials; } + bool isEquipment() const { return mActivity == gb_Activity.Equipments; } + + int getActivity() const { return mActivity; } + + void toggleWeaponMenu() + { + switch (mActivity) + { + case gb_Activity.Inventory: case gb_Activity.Specials: + case gb_activity.Equipments: + case gb_Activity.None: mActivity = gb_Activity.Weapons; break; + case gb_Activity.Weapons: mActivity = gb_Activity.None; break; + } + } + + void close() { mActivity = gb_Activity.None; } + void openWeapons() { mActivity = gb_Activity.Weapons; } + void openInventory() { mActivity = gb_Activity.Inventory; } + void openSpecials() { mActivity = gb_Activity.Specials; } + void openEquipment() { mActivity = gb_Activity.Equipments; } + +// private: //////////////////////////////////////////////////////////////////////////////////////// + + enum Activity + { + + None, + Weapons, + Inventory, + Specials, + Equipments, + } + + private int mActivity; + +} // class gb_Activity diff --git a/zscript/gearbox/changer.zs b/zscript/gearbox/changer.zs new file mode 100644 index 0000000000..7849041488 --- /dev/null +++ b/zscript/gearbox/changer.zs @@ -0,0 +1,89 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2020-2021 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +class gb_Changer play +{ + + static + gb_Changer from(gb_Caption caption, gb_Options options, gb_InventoryUser inventoryUser) + { + let result = new("gb_Changer"); + result.mCaption = caption; + result.mOptions = options; + result.mInventoryUser = inventoryUser; + return result; + } + + void selectWeapon(PlayerInfo player, string weapon) + { + Weapon targetWeapon = Weapon(player.mo.findInventory(weapon)); + if (targetWeapon && gb_WeaponWatcher.currentFor(player) != targetWeapon.getClass()) + { + player.pendingWeapon = targetWeapon; + if (mOptions.isShowingWeaponTagsOnChange()) mCaption.setActor(targetWeapon); + } + } + + void useItem(PlayerInfo player, string item) + { + mInventoryUser.addToQueue(player, item); + } + + void giveItem(PlayerInfo player, string item) //added to, well, give items to the player + { + player.mo.A_giveinventory(item,1); + } + + static + void setAngles(PlayerInfo player, double pitch, double angle) + { + if (player.mo == NULL) return; + + player.cheats |= CF_InterpView; + player.mo.pitch = pitch; + player.mo.angle = angle; + + // To prevent mods that add weapon sway from swaying while moving mouse in wheel. + player.cmd.yaw = 0; + player.cmd.pitch = 0; + } + + static + void freezePlayer( PlayerInfo player + , int cheats + , double velocityX + , double velocityY + , double velocityZ + , double gravity + ) + { + if (player.mo == NULL) return; + + vector3 velocity = (velocityX, velocityY, velocityZ); + player.cheats = cheats; + player.vel = velocity.xy; + player.mo.vel = velocity; + player.mo.gravity = gravity; + } + +// private: //////////////////////////////////////////////////////////////////////////////////////// + + private gb_Caption mCaption; + private gb_Options mOptions; + private gb_InventoryUser mInventoryUser; + +} // class gb_Changer diff --git a/zscript/gearbox/custom_weapon_order_storage.zs b/zscript/gearbox/custom_weapon_order_storage.zs new file mode 100644 index 0000000000..c094726f83 --- /dev/null +++ b/zscript/gearbox/custom_weapon_order_storage.zs @@ -0,0 +1,186 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2020-2021 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +class gb_WeaponOrderOperations +{ + + static + gb_WeaponOrderOperations from() + { + let result = new("gb_WeaponOrderOperations"); + result.index.clear(); + result.operationType.clear(); + return result; + } + + Array index; + Array operationType; + +} // class gb_WeaponOrderOperations + +class gb_CustomWeaponOrderStorage +{ + + enum OperationType + { + RotatePriority, + RotateSlot, + } + + static + string calculateHash(gb_WeaponData data) + { + string dataString; + + uint nWeapons = data.weapons.size(); + for (uint i = 0; i < nWeapons; ++i) + { + dataString.appendFormat("%s%d", data.weapons[i].getClassName(), data.slots[i]); + } + + return gb_MD5.hash(dataString, false); + } + + static + void applyOperations(string weaponSetHash, gb_WeaponMenu menu) + { + let operations = gb_WeaponOrderOperations.from(); + string operationsData = loadFromStorage(weaponSetHash); + deserializeOperations(operationsData, operations); + + uint nOperations = operations.index.size(); + for (uint i = 0; i < nOperations; ++i) + { + switch (operations.operationType[i]) + { + case RotatePriority: + menu.rotatePriorityForIndex(operations.index[i]); + break; + + case RotateSlot: + menu.rotateSlotForIndex(operations.index[i]); + break; + + default: + Console.printf("Unknown operation: %d.", operations.operationType[i]); + } + } + } + + static + void savePriorityRotation(string weaponSetHash, int index) + { + saveOperation(weaponSetHash, index, RotatePriority); + } + + static + void saveSlotRotation(string weaponSetHash, int index) + { + saveOperation(weaponSetHash, index, RotateSlot); + } + + static + void reset(string weaponSetHash) + { + Dictionary weaponSetOrders = readWeaponSetOrders(); + weaponSetOrders.remove(weaponSetHash); + getStorageCvar().setString(weaponSetOrders.toString()); + } + +// private: //////////////////////////////////////////////////////////////////////////////////////// + + static + void saveOperation(string weaponSetHash, int index, int operationType) + { + let operations = gb_WeaponOrderOperations.from(); + { + string operationsData = loadFromStorage(weaponSetHash); + deserializeOperations(operationsData, operations); + } + + operations.index .push(index); + operations.operationType.push(operationType); + + string operationsData = serializeOperations(operations); + saveToStorage(weaponSetHash, operationsData); + } + + private static + void saveToStorage(string hash, string data) + { + Dictionary weaponSetOrders = readWeaponSetOrders(); + weaponSetOrders.insert(hash, data); + getStorageCvar().setString(weaponSetOrders.toString()); + } + + private static + string loadFromStorage(string hash) + { + return readWeaponSetOrders().at(hash); + } + + private static + Dictionary readWeaponSetOrders() + { + return Dictionary.fromString(getStorageCvar().getString()); + } + + private static + Cvar getStorageCvar() + { + return Cvar.getCvar(STORAGE_CVAR_NAME); + } + + private static + string serializeOperations(gb_WeaponOrderOperations operations) + { + string result; + uint nOperations = operations.index.size(); + for (uint i = 0; i < nOperations; ++i) + { + result.appendFormat( "%d" .. DELIMITER .. "%d" .. DELIMITER + , operations.index[i] + , operations.operationType[i] + ); + } + return result; + } + + private static + void deserializeOperations(string data, out gb_WeaponOrderOperations operations) + { + Array tokens; + data.split(tokens, DELIMITER, TOK_SkipEmpty); + + if (tokens.size() % 2 != 0) + { + Console.printf("Weapon order data is corrupted!"); + return; + } + + uint nTokens = tokens.size(); + for (uint i = 0; i < nTokens; i += 2) + { + operations.index .push(tokens[i ].toInt()); + operations.operationType.push(tokens[i + 1].toInt()); + } + } + + const STORAGE_CVAR_NAME = "gb_custom_weapon_order"; + const DELIMITER = ";"; + +} // class gb_CustomWeaponOrderStorage diff --git a/zscript/gearbox/display/blocky_view.zs b/zscript/gearbox/display/blocky_view.zs new file mode 100644 index 0000000000..55957c0b57 --- /dev/null +++ b/zscript/gearbox/display/blocky_view.zs @@ -0,0 +1,518 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2020-2021 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +class gb_BlockyView +{ + + static + gb_BlockyView from(gb_TextureCache textureCache, gb_Options options, gb_FontSelector fontSelector) + { + let result = new("gb_BlockyView"); + + result.setAlpha(1.0); + result.setScale(1); + result.setBaseColor(0x2222CC); + result.mTextureCache = textureCache; + result.mOptions = options; + result.mFontSelector = fontSelector; + + return result; + } + + void setAlpha(double alpha) + { + mAlpha = alpha; + } + + void setScale(int scale) + { + if (scale < 1) scale = 1; + + double scaleFactor = getScaleFactor(); + + mScreenWidth = int(Screen.getWidth() / scale / scaleFactor); + mScreenHeight = int(Screen.getHeight() / scale / scaleFactor); + } + + void setBaseColor(int color) + { + mBaseColor = color; + mSlotColor = addColor(mBaseColor, -0x22); + mSelectedColor = addColor(mBaseColor, 0x44); + mAmmoBackColor = addColor(mBaseColor, 0x66); + } + + void display(gb_ViewModel viewModel) const + { + int selectedIndex = viewModel.selectedIndex; + if (selectedIndex >= viewModel.slots.size() || selectedIndex == -1) return; + + vector2 position = mOptions.getBlocksPosition(); + int maxWidth = getMaxWidth(getSlotsNumber(viewModel)); + int maxHeight = getMaxHeight(viewModel); + int startX = int(min(BORDER + (mScreenWidth - BORDER) * position.x, mScreenWidth - maxWidth)); + int startY = int(min(BORDER + (mScreenHeight - BORDER) * position.y, mScreenHeight - maxHeight)); + + int lastDrawnSlot = 0; + int slotX = startX; + int inSlotIndex = 0; + + int selectedSlot = viewModel.slots[selectedIndex]; + + Font aFont = mFontSelector.getFont(); + int fontHeight = aFont.getHeight(); + int textY = startY + SLOT_SIZE / 2 - fontHeight / 2; + + uint nWeapons = viewModel.tags.size(); + for (uint i = 0; i < nWeapons; ++i) + { + int slot = viewModel.slots[i]; + + // slot number box + if (slot != lastDrawnSlot) + { + drawAlphaTexture(mTextureCache.blockBox, slotX, startY, mSlotColor,false); + + string slotText = string.format("%d", slot); + int textX = slotX + SLOT_SIZE / 2 - aFont.stringWidth(slotText) / 2; + + int col = (slot == selectedSlot) ? Font.CR_YELLOW : Font.CR_WHITE; + drawText(aFont, col, textX, textY, slotText); + + lastDrawnSlot = slot; + } + + if (slot == selectedSlot) // selected slot (big boxes) + { + bool isSelectedWeapon = (i == selectedIndex); + int weaponColor = isSelectedWeapon ? mSelectedColor : mBaseColor; + int weaponY = startY + SLOT_SIZE + (SELECTED_WEAPON_HEIGHT + MARGIN) * inSlotIndex; + + // big box blockBigSel + drawAlphaTexture(isSelectedWeapon ? mTextureCache.blockBigSel : mTextureCache.blockBig, + slotX, weaponY, weaponColor,false); + + // weapon + { + // code is adapted from GZDoom AltHud.DrawImageToBox. + TextureID weaponTexture = viewModel.icons[i]; + let weaponSize = TexMan.getScaledSize(weaponTexture); + weaponSize.x *= viewModel.iconScaleXs[i]; + weaponSize.y *= viewModel.iconScaleYs[i]; + if (mOptions.isPreservingAspectRatio()) weaponSize.y *= 1.2; + + int allowedWidth = SELECTED_SLOT_WIDTH - MARGIN * 2; + int allowedHeight = SELECTED_WEAPON_HEIGHT - MARGIN * 2; + + double scaleHor = (allowedWidth < weaponSize.x) ? allowedWidth / weaponSize.x : 1.0; + double scaleVer = (allowedHeight < weaponSize.y) ? allowedHeight / weaponSize.y : 1.0; + double scale = min(scaleHor, scaleVer); + + int weaponWidth = int(round(weaponSize.x * scale)); + int weaponHeight = int(round(weaponSize.y * scale)); + + drawWeapon( weaponTexture + , slotX + SELECTED_SLOT_WIDTH / 2 + , weaponY + SELECTED_WEAPON_HEIGHT / 2 + , weaponWidth + , weaponHeight + ); + } + + // corners + if (isSelectedWeapon) + { + int lineEndX = slotX + SELECTED_SLOT_WIDTH - CORNER_SIZE; + int lineEndY = weaponY + SELECTED_WEAPON_HEIGHT - CORNER_SIZE; + TextureID cornerTexture = mTextureCache.corner; + // top left, top right, bottom left, bottom right + drawFlippedTexture(cornerTexture, slotX, weaponY, NoHorizontalFlip, NoVerticalFlip); + drawFlippedTexture(cornerTexture, lineEndX, weaponY, HorizontalFlip, NoVerticalFlip); + drawFlippedTexture(cornerTexture, slotX, lineEndY, NoHorizontalFlip, VerticalFlip); + drawFlippedTexture(cornerTexture, lineEndX, lineEndY, HorizontalFlip, VerticalFlip); + } + + // quantity indicators + TextureID ammoTexture = mTextureCache.ammoLine; + int ammoY = weaponY; + if (gb_Ammo.isValid(viewModel.quantity1[i], viewModel.maxQuantity1[i])) + { + drawAlphaTexture( ammoTexture + , slotX + MARGIN * 2 + , ammoY + , EMPTY_AMMO_COLOR//mAmmoBackColor + ); + int ammoRatioWidth = ammoRatioWidthFor(viewModel.quantity1[i], viewModel.maxQuantity1[i]); + drawAlphaWidthTexture( ammoTexture + , slotX + MARGIN * 2 + , ammoY + , FILLED_AMMO_COLOR + , ammoRatioWidth + ); + ammoY += MARGIN + AMMO_HEIGHT; + } + if (gb_Ammo.isValid(viewModel.quantity2[i], viewModel.maxQuantity2[i])) + { + drawAlphaTexture( ammoTexture + , slotX + MARGIN * 2 + , ammoY + , EMPTY_AMMO_COLOR//mAmmoBackColor + ); + int ammoRatioWidth = ammoRatioWidthFor(viewModel.quantity2[i], viewModel.maxQuantity2[i]); + drawAlphaWidthTexture( ammoTexture + , slotX + MARGIN * 2 + , ammoY + , FILLED_AMMO_COLOR + , ammoRatioWidth + ); + } + + if (mOptions.isShowingTags()) drawTag(viewModel.tags[i], aFont, slotX, weaponY); + } + else // not selected slot (small boxes) + { + int boxY = startY - MARGIN + (SLOT_SIZE + MARGIN) * (inSlotIndex + 1); + drawAlphaTexture(mTextureCache.blockBox, slotX, boxY, mBaseColor,false); + } + + if (i + 1 < nWeapons && viewModel.slots[i + 1] != slot) + { + slotX += ((slot == selectedSlot) ? SELECTED_SLOT_WIDTH : SLOT_SIZE) + MARGIN; + inSlotIndex = 0; + } + else + { + ++inSlotIndex; + } + } + } + +// private: //////////////////////////////////////////////////////////////////////////////////////// + + private static + int getSlotsNumber(gb_ViewModel viewModel) + { + uint nEntries = viewModel.slots.size(); + int nSlots = 0; + int lastSlot = -1; + for (uint i = 0; i < nEntries; ++i) + { + if (viewModel.slots[i] != lastSlot) + { + ++nSlots; + lastSlot = viewModel.slots[i]; + } + } + return nSlots; + } + + private static + int getMaxWidth(int nSlots) + { + return (nSlots - 1) * (SLOT_SIZE + MARGIN) + SELECTED_SLOT_WIDTH + BORDER; + } + + private static + int getMaxHeight(gb_ViewModel viewModel) + { + uint nEntries = viewModel.slots.size(); + int lastSlot = -1; + int itemsInSlot = 1; + int maxItemsInSlot = -1; + for (uint i = 0; i < nEntries; ++i) + { + if (viewModel.slots[i] == lastSlot) + { + ++itemsInSlot; + } + else + { + lastSlot = viewModel.slots[i]; + if (itemsInSlot > maxItemsInSlot) maxItemsInSlot = itemsInSlot; + itemsInSlot = 1; + } + } + if (itemsInSlot > maxItemsInSlot) maxItemsInSlot = itemsInSlot; + + return maxItemsInSlot * (SELECTED_WEAPON_HEIGHT + MARGIN) - MARGIN + BORDER + SLOT_SIZE; + } + + private + void drawTag(string tag, Font aFont, double startX, double startY) + { + // >_< + // + // Trying to put as much text into the the box as possible, while gracefully + // (hopefully) handling when the whole tag cannot fit in the box. + // + // This code doesn't take the icon and quantity bars into account. Just print + // semi-transparent text over them. + + if (tag.length() == 0) return; + + Array words; + tag.split(words, " ", Tok_SkipEmpty); + + // Start filling lines with words from bottom to top. If the word doesn't + // fit into the line, it is pushed into the new top line. + Array lines; + int nWords = words.size(); + lines.push(words[nWords - 1]); + int spaceWidth = aFont.stringWidth(" "); + int allowedStringWidth = SELECTED_SLOT_WIDTH - 2; + for (int i = nWords - 2; i >= 0; --i) + { + uint newWidth = (aFont.stringWidth(lines[0]) + spaceWidth + aFont.stringWidth(words[i])); + if (newWidth < allowedStringWidth) + { + lines[0] = words[i] .. " " .. lines[0]; + } + else + { + lines.insert(0, words[i]); + } + } + + // If there are too many lines, put them on the third line and mark the it + // with ellipsis. + string ellipsis = aFont.getGlyphHeight(ELLIPSIS_CODE) ? "…" : "..."; + uint nLines = lines.size(); + if (nLines > 3) + { + for (uint i = 3; i < nLines; ++i) + { + lines[2].appendFormat(" %s", lines[i]); + } + lines[2] = lines[2] .. ellipsis; + } + + // If a line is too long to fit in the box, replace the part that doesn't + // fit with ellipsis. + uint linesEnd = min(nLines, 3); + int ellipsisWidth = aFont.stringWidth(ellipsis); + for (uint i = 0; i < linesEnd; ++i) + { + if (aFont.stringWidth(lines[i]) <= allowedStringWidth) continue; + + while (aFont.stringWidth(lines[i]) + ellipsisWidth > allowedStringWidth) + { + lines[i].deleteLastCharacter(); + } + lines[i] = lines[i] .. ellipsis; + } + + // Finally, print lines. + int lineHeight = aFont.getHeight(); + for (uint i = 0; i < linesEnd; ++i) + { + double y = startY + SELECTED_WEAPON_HEIGHT + (i - linesEnd) * lineHeight; + drawText(aFont, Font.CR_WHITE, startX + 1, y, lines[i], 0.3); + } + } + + private static + int ammoRatioWidthFor(int ammoCount, int ammoMax) + { + return int(round(float(ammoCount) / ammoMax * AMMO_WIDTH)); + } + + private + void drawAlphaTexture(TextureID texture, int x, int y, int color, bool docolor = true) const + { + if(docolor) + { + Screen.drawTexture( texture + , NO_ANIMATION + , x + , y + , DTA_FillColor , color + , DTA_AlphaChannel , true + , DTA_Alpha , mAlpha + , DTA_VirtualWidth , mScreenWidth + , DTA_VirtualHeight , mScreenHeight + , DTA_KeepRatio , true + ); + } + else + { + Screen.drawTexture( texture + , NO_ANIMATION + , x + , y + , DTA_AlphaChannel , true + , DTA_Alpha , mAlpha + , DTA_VirtualWidth , mScreenWidth + , DTA_VirtualHeight , mScreenHeight + , DTA_KeepRatio , true + ); + + } + } + + private + void drawAlphaWidthTexture(TextureID texture, int x, int y, int color, int width,bool docolor = true) const + { + if(docolor) + { + Screen.drawTexture( texture + , NO_ANIMATION + , x + , y + , DTA_FillColor , color + , DTA_AlphaChannel , true + , DTA_Alpha , mAlpha + , DTA_VirtualWidth , mScreenWidth + , DTA_VirtualHeight , mScreenHeight + , DTA_KeepRatio , true + , DTA_DestWidth , width + ); + } + else + { + Screen.drawTexture( texture + , NO_ANIMATION + , x + , y + , DTA_AlphaChannel , true + , DTA_Alpha , mAlpha + , DTA_VirtualWidth , mScreenWidth + , DTA_VirtualHeight , mScreenHeight + , DTA_KeepRatio , true + , DTA_DestWidth , width + ); + } + } + + enum HorizontalFlipOptions + { + NoHorizontalFlip = 0, + HorizontalFlip = 1, + } + + enum VerticalFlipOptions + { + NoVerticalFlip = 0, + VerticalFlip = 1, + } + + private + void drawFlippedTexture(TextureID texture, int x, int y, int horFlip, int verFlip) const + { + Screen.drawTexture( texture + , NO_ANIMATION + , x + , y + , DTA_Alpha , mAlpha + , DTA_VirtualWidth , mScreenWidth + , DTA_VirtualHeight , mScreenHeight + , DTA_KeepRatio , true + , DTA_FlipX , horFlip + , DTA_FlipY , verFlip + ); + } + + private + void drawWeapon(TextureID texture, int x, int y, int w, int h) const + { + Screen.drawTexture( texture + , NO_ANIMATION + , x + , y + , DTA_CenterOffset , true + , DTA_KeepRatio , true + , DTA_DestWidth , w + , DTA_DestHeight , h + , DTA_Alpha , mAlpha + , DTA_VirtualWidth , mScreenWidth + , DTA_VirtualHeight , mScreenHeight + ); + } + + private + void drawText(Font aFont, int color, double x, double y, string text, double alpha = 1.0) const + { + Screen.drawText( aFont + , color + , x + , y + , text + , DTA_Alpha , mAlpha * alpha + , DTA_VirtualWidth , mScreenWidth + , DTA_VirtualHeight , mScreenHeight + , DTA_KeepRatio , true + ); + } + + private static + color addColor(color base, int add) + { + uint newRed = clamp(base.r + add, 0, 255); + uint newGreen = clamp(base.g + add, 0, 255); + uint newBlue = clamp(base.b + add, 0, 255); + + if ( abs(int(newRed ) - base.r) < abs(add) + && abs(int(newGreen) - base.g) < abs(add) + && abs(int(newBlue ) - base.b) < abs(add) + ) + { + return addColor(base, -add); + } + + uint result = (newRed << 16) + (newGreen << 8) + newBlue; + return result; + } + + private static + double getScaleFactor() + { + return Screen.getHeight() / 1080.0; + } + + const BORDER = 20; + const MARGIN = 4; + + const SLOT_SIZE = 25; + const SELECTED_SLOT_WIDTH = 100; + const SELECTED_WEAPON_HEIGHT = SLOT_SIZE * 2 + MARGIN; + + const NO_ANIMATION = 0; // == false + + const CORNER_SIZE = 4; + + const AMMO_WIDTH = 40; + const AMMO_HEIGHT = 6; + + const FILLED_AMMO_COLOR = 0x00F200; //0x22DD22; + const EMPTY_AMMO_COLOR = 0xE6010D; + + const ELLIPSIS_CODE = 8230; + + private double mAlpha; + + private int mScreenWidth; + private int mScreenHeight; + + private color mBaseColor; + private color mSlotColor; + private color mSelectedColor; + private color mAmmoBackColor; + + private gb_TextureCache mTextureCache; + private gb_Options mOptions; + private gb_FontSelector mFontSelector; + +} // class gb_BlockyView diff --git a/zscript/gearbox/display/blur.zs b/zscript/gearbox/display/blur.zs new file mode 100644 index 0000000000..ec1651877d --- /dev/null +++ b/zscript/gearbox/display/blur.zs @@ -0,0 +1,27 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2021 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +class gb_Blur +{ + + static + void setEnabled(bool isEnabled) + { + Shader.setEnabled(players[consolePlayer], "gb_blur", isEnabled); + } + +} // class gb_Blur diff --git a/zscript/gearbox/display/caption.zs b/zscript/gearbox/display/caption.zs new file mode 100644 index 0000000000..46bcc3d258 --- /dev/null +++ b/zscript/gearbox/display/caption.zs @@ -0,0 +1,47 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2021 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +class gb_Caption +{ + + static + gb_Caption from() + { + let result = new("gb_Caption"); + result.mActor = NULL; + return result; + } + + void setActor(Actor anActor) + { + mActor = anActor; + } + + ui + void show() + { + if (mActor == NULL) return; + + mActor.displayNameTag(); + mActor = NULL; + } + +// private: //////////////////////////////////////////////////////////////////////////////////////// + + private Actor mActor; + +} // class gb_Caption diff --git a/zscript/gearbox/display/dim.zs b/zscript/gearbox/display/dim.zs new file mode 100644 index 0000000000..3c756a860a --- /dev/null +++ b/zscript/gearbox/display/dim.zs @@ -0,0 +1,29 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2020-2021 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +class gb_Dim +{ + + static + void dim(double alpha, gb_Options options) + { + if (!options.isDimEnabled()) return; + + Screen.Dim(options.getDimColor(), alpha * 0.4, 0, 0, Screen.getWidth(), Screen.getHeight()); + } + +} // class gb_Dim diff --git a/zscript/gearbox/display/fade_in_out.zs b/zscript/gearbox/display/fade_in_out.zs new file mode 100644 index 0000000000..7682a17476 --- /dev/null +++ b/zscript/gearbox/display/fade_in_out.zs @@ -0,0 +1,43 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2020-2021 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +class gb_FadeInOut +{ + + static + gb_FadeInOut from() + { + let result = new("gb_FadeInOut"); + result.mAlpha = 0.0; + return result; + } + + void fadeInOut(double step) + { + mAlpha = clamp(mAlpha + step, 0.0, 1.0); + } + + double getAlpha() const + { + return mAlpha; + } + +// private: //////////////////////////////////////////////////////////////////////////////////////// + + private double mAlpha; + +} diff --git a/zscript/gearbox/display/text_view.zs b/zscript/gearbox/display/text_view.zs new file mode 100644 index 0000000000..0c557257f9 --- /dev/null +++ b/zscript/gearbox/display/text_view.zs @@ -0,0 +1,191 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2021 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +class gb_TextView +{ + + static + gb_TextView from(gb_Options options, gb_FontSelector fontSelector) + { + let result = new("gb_TextView"); + + result.setAlpha(1.0); + result.setScale(1); + result.mOptions = options; + result.mFontSelector = fontSelector; + + return result; + } + + void setAlpha(double alpha) + { + mAlpha = alpha; + } + + void setScale(int scale) + { + if (scale < 1) scale = 1; + + double scaleFactor = getScaleFactor(); + + mScreenWidth = int(Screen.getWidth() / scale / scaleFactor); + mScreenHeight = int(Screen.getHeight() / scale / scaleFactor); + } + + void display(gb_ViewModel viewModel) const + { + uint nWeapons = viewModel.tags.size(); + if (nWeapons == 0) return; + + vector2 start = mOptions.getTextPosition(); + start.x *= mScreenWidth; + start.y *= mScreenHeight; + double x = start.x; + double y = start.y; + Font aFont = mFontSelector.getFont(); + double lineHeight = aFont.getHeight(); + int usualColor = mOptions.getTextUsualColor(); + int selectedColor = mOptions.getTextSelectedColor(); + + double maxX = 0; + double maxY = 0; + + // dry run + int maxSlotWidth = 0; + for (uint i = 0; i < nWeapons; ++i) + { + string slotString = string.format("%d. ", viewModel.slots[i]); + maxSlotWidth = max(maxSlotWidth, aFont.stringWidth(slotString)); + } + for (uint i = 0; i < nWeapons; ++i) + { + string itemString = makeItemString(viewModel, i); + maxX = max(maxX, maxSlotWidth + x + aFont.stringWidth(itemString)); + y += lineHeight; + if (i + 1 < nWeapons && viewModel.slots[i] != viewModel.slots[i + 1]) y += lineHeight / 3; + } + maxY = y; + + if (maxX > mScreenWidth) x = max(0, mScreenWidth - (maxX - x)); + + double yBoundary = mScreenHeight * mOptions.getTextPositionYMax(); + if (maxY > yBoundary && (maxY - lineHeight != start.y)) + { + lineHeight *= (yBoundary - lineHeight - start.y) / (maxY - lineHeight - start.y); + } + + y = start.y; + + // real run + double selectedY = 0; + for (uint i = 0; i < nWeapons; ++i) + { + if (i == viewModel.selectedIndex) + { + selectedY = y; + } + else + { + string slotString = string.format("%d. ", viewModel.slots[i]); + drawText(aFont, usualColor, x, y, slotString, mAlpha); + } + y += lineHeight; + if (i + 1 < nWeapons && viewModel.slots[i] != viewModel.slots[i + 1]) y += lineHeight / 3; + } + { + string slotString = string.format("%d. ", viewModel.slots[viewModel.selectedIndex]); + drawText(aFont, selectedColor, x, selectedY, slotString, mAlpha); + } + + y = start.y; + + for (uint i = 0; i < nWeapons; ++i) + { + if (i != viewModel.selectedIndex) + { + string itemText = makeItemString(viewModel, i); + drawText(aFont, usualColor, x + maxSlotWidth, y, itemText, mAlpha); + } + y += lineHeight; + if (i + 1 < nWeapons && viewModel.slots[i] != viewModel.slots[i + 1]) y += lineHeight / 3; + } + { + string itemText = makeItemString(viewModel, viewModel.selectedIndex); + drawText(aFont, selectedColor, x + maxSlotWidth, selectedY, itemText, mAlpha); + } + } + +// private: //////////////////////////////////////////////////////////////////////////////////////// + + private + string makeItemString(gb_ViewModel viewModel, int i) + { + string result = viewModel.tags[i]; + + bool isQuantity1Valid = gb_Ammo.isValid(viewModel.quantity1[i], viewModel.maxQuantity1[i]); + bool isQuantity2Valid = gb_Ammo.isValid(viewModel.quantity2[i], viewModel.maxQuantity2[i]); + + if (isQuantity1Valid && isQuantity2Valid) + { + result.appendFormat(" (%d/%d, %d/%d)" + , viewModel.quantity1[i] + , viewModel.maxQuantity1[i] + , viewModel.quantity2[i] + , viewModel.maxQuantity2[i] + ); + } + else if (isQuantity1Valid) + { + result.appendFormat(" (%d/%d)", viewModel.quantity1[i], viewModel.maxQuantity1[i]); + } + else if (isQuantity2Valid) + { + result.appendFormat(" (%d/%d)", viewModel.quantity2[i], viewModel.maxQuantity2[i]); + } + + return result; + } + + private + void drawText(Font aFont, int color, double x, double y, string text, double alpha = 1.0) const + { + Screen.drawText( aFont + , color + , x + , y + , text + , DTA_Alpha , mAlpha * alpha + , DTA_VirtualWidth , mScreenWidth + , DTA_VirtualHeight , mScreenHeight + , DTA_KeepRatio , true + ); + } + + private static + double getScaleFactor() + { + return Screen.getHeight() / 1080.0; + } + + private double mAlpha; + private int mScreenWidth; + private int mScreenHeight; + + private gb_Options mOptions; + private gb_FontSelector mFontSelector; + +} // class gb_TextView diff --git a/zscript/gearbox/engine/level.zs b/zscript/gearbox/engine/level.zs new file mode 100644 index 0000000000..1ae3ef8dfb --- /dev/null +++ b/zscript/gearbox/engine/level.zs @@ -0,0 +1,35 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2020-2021 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +class gb_Level +{ + + enum _ { NotInGame, Loading, JustLoaded, Loaded } + + static + int getState() + { + // In the best case, the world is fully loaded on tick 0, but if player + // weapons are configured via keyconf, they require a bit of network + // communication, which finishes at tick 1. + if (level.mapName ~== "titlemap") return gb_Level.NotInGame; + if (level.mapTime == 0) return gb_Level.Loading; + if (level.mapTime == 1) return gb_Level.JustLoaded; + return gb_Level.Loaded; + } + +} // class gb_Level diff --git a/zscript/gearbox/engine/player.zs b/zscript/gearbox/engine/player.zs new file mode 100644 index 0000000000..8ccef2fce4 --- /dev/null +++ b/zscript/gearbox/engine/player.zs @@ -0,0 +1,27 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2020-2021 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +class gb_Player +{ + + static + bool isDead() + { + return (players[consolePlayer].mo.health <= 0); + } + +} // class gb_Player diff --git a/zscript/gearbox/engine/weapon_watcher.zs b/zscript/gearbox/engine/weapon_watcher.zs new file mode 100644 index 0000000000..5714306daf --- /dev/null +++ b/zscript/gearbox/engine/weapon_watcher.zs @@ -0,0 +1,40 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2020-2021 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +/** + * This class provides information on what weapon the players hold. + */ +class gb_WeaponWatcher +{ + + static + class current() + { + return currentFor(players[consolePlayer]); + } + + static + class currentFor(PlayerInfo player) + { + let currentWeapon = player.pendingWeapon.getClassName() != "Object" + ? player.pendingWeapon + : player.readyWeapon; + + return currentWeapon ? currentWeapon.getClass() : NULL; + } + +} // class gb_WeaponWatcher diff --git a/zscript/gearbox/event_handler.zs b/zscript/gearbox/event_handler.zs new file mode 100644 index 0000000000..6ffcc7d37c --- /dev/null +++ b/zscript/gearbox/event_handler.zs @@ -0,0 +1,632 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2020-2022 + * Carrascado 2022 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +/** + * This class is the core of Gearbox. + * + * It delegates as much work to other classes while minimizing the relationships + * between those classes. + * + * To ensure multiplayer compatibility, Gearbox does the following: + * + * 1. All visuals and input processing happens on client side and is invisible + * to the network. + * + * 2. Actual game changing things, like switching weapons, are done through + * network - even for the current player, even for the single-player game. + */ +class gb_EventHandler : EventHandler +{ + + override + void worldTick() + { + switch (gb_Level.getState()) + { + case gb_Level.NotInGame: return; + case gb_Level.Loading: return; + case gb_Level.JustLoaded: initialize(); // fall through + case gb_Level.Loaded: break; + } + + bool isClosed = mActivity.isNone(); + + // Thaw regardless of the option to prevent player being locked frozen after + // changing options. + if (isClosed) mFreezer.thaw(); + else mFreezer.freeze(); + + if (!isClosed && (gb_Player.isDead() || isDisabledOnAutomap())) + { + close(); + } + + if (isClosed) + { + // Watch for the current weapon, because player can change it without + // Gearbox. Also handles the case when Gearbox hasn't been opened yet, + // initializing weapon menu. + mWeaponMenu.setSelectedWeapon(gb_WeaponWatcher.current()); + } + else if (mOptions.getViewType() == VIEW_TYPE_WHEEL || mActivity.isSpecials() || mActivity.isequipment()) + { + mWheelController.process(); + } + + mInventoryUser.use(); + } + + /** + * This function processes key bindings specific for Gearbox. + */ + override + void consoleProcess(ConsoleEvent event) + { + if (players[consolePlayer].mo == NULL) return; + + if (!mIsInitialized || isDisabledOnAutomap()) return; + if (isPlayerFrozen() && mActivity.isNone()) return; + + switch (gb_EventProcessor.process(event, mOptions.isSelectOnKeyUp())) + { + case InputToggleWeaponMenu: toggleWeapons(); break; + case InputConfirmSelection: confirmSelection(); close(); break; + case InputToggleInventoryMenu: toggleInventory(); break; + case InputRotateWeaponPriority: rotateWeaponPriority(); break; + case InputRotateWeaponSlot: rotateWeaponSlot(); break; + case InputToggleSpecialMenu: toggleSpecials(); break; + case InputToggleEquipMenu: toggleEquipments(); break; + } + + if (!mActivity.isNone()) mWheelController.reset(); + } + + /** + * This function provides latching to existing key bindings, and processing mouse input. + */ + override + bool inputProcess(InputEvent event) + { + if (players[consolePlayer].mo == NULL) return false; + if (!mIsInitialized || isDisabledOnAutomap() || gameState != GS_LEVEL) return false; + if (isPlayerFrozen() && mActivity.isNone()) return false; + + int input = gb_InputProcessor.process(event); + + if (mActivity.isWeapons()) + { + switch (input) + { + case InputSelectNextWeapon: tickIf(mWeaponMenu.selectNextWeapon()); mWheelController.reset(); break; + case InputSelectPrevWeapon: tickIf(mWeaponMenu.selectPrevWeapon()); mWheelController.reset(); break; + case InputConfirmSelection: confirmSelection(); close(); break; + case InputClose: close(); break; + + default: + if (!gb_Input.isSlot(input)) return false; + mWheelController.reset(); + tickIf(mWeaponMenu.selectSlot(gb_Input.getSlot(input))); + break; + } + + return true; + } + else if (mActivity.isInventory()) + { + switch (input) + { + case InputSelectNextWeapon: tickIf(mInventoryMenu.selectNext()); mWheelController.reset(); break; + case InputSelectPrevWeapon: tickIf(mInventoryMenu.selectPrev()); mWheelController.reset(); break; + case InputConfirmSelection: confirmSelection(); close(); break; + case InputClose: close(); break; + + default: + { + if (!gb_Input.isSlot(input)) return false; + mWheelController.reset(); + int slot = gb_Input.getSlot(input); + int index = (slot == 0) ? 9 : slot - 1; + tickIf(mInventoryMenu.setSelectedIndex(index)); + break; + } + } + return true; + } + else if(mActivity.isSpecials()) + { + switch (input) + { + case InputSelectNextWeapon: tickIf(mspecialsmenu.selectNext()); mWheelController.reset(); break; + case InputSelectPrevWeapon: tickIf(mspecialsmenu.selectPrev()); mWheelController.reset(); break; + case InputConfirmSelection: confirmSelection(); close(); break; + case InputClose: close(); break; + + default: + { + if (!gb_Input.isSlot(input)) return false; + mWheelController.reset(); + int slot = gb_Input.getSlot(input); + int index = (slot == 0) ? 9 : slot - 1; + tickIf(mspecialsmenu.setSelectedIndex(index)); + break; + } + } + return true; + } + else if (mactivity.isEquipment()) + { + //gb_Activity.Equipments + switch (input) + { + case InputSelectNextWeapon: tickIf(mEquipmenu.selectNext()); mWheelController.reset(); break; + case InputSelectPrevWeapon: tickIf(mEquipmenu.selectPrev()); mWheelController.reset(); break; + case InputConfirmSelection: confirmSelection(); close(); break; + case InputClose: close(); break; + + default: + { + if (!gb_Input.isSlot(input)) return false; + mWheelController.reset(); + int slot = gb_Input.getSlot(input); + int index = (slot == 0) ? 9 : slot - 1; + tickIf(mEquipmenu.setSelectedIndex(index)); + break; + } + } + return true; + } + else if (mActivity.isNone()) + { + mWheelController.reset(); + + if (gb_Input.isSlot(input) && mOptions.isOpenOnSlot()) + { + int slot = gb_Input.getSlot(input); + + if (mOptions.isNoMenuIfOne() && mWeaponMenu.isOneWeaponInSlot(slot)) + { + tickIf(mWeaponMenu.selectSlot(slot)); + gb_Sender.sendSelectEvent(mWeaponMenu.confirmSelection()); + } + else if (mWeaponMenu.selectSlot(slot, mOptions.isSelectFirstSlotWeapon())) + { + mSounds.playOpen(); + mActivity.openWeapons(); + } + else + { + mSounds.playNope(); + return false; + } + + return true; + } + + if (!mOptions.isOpenOnScroll()) return false; + + switch (input) + { + case InputSelectNextWeapon: toggleWeapons(); mWeaponMenu.selectNextWeapon(); return true; + case InputSelectPrevWeapon: toggleWeapons(); mWeaponMenu.selectPrevWeapon(); return true; + } + } + + return false; + } + + override + void networkProcess(ConsoleEvent event) + { + if (players[consolePlayer].mo == NULL) return; + + int input = mNeteventProcessor.process(event); + + switch (input) + { + case InputResetCustomOrder: resetCustomOrder(); break; + } + } + + override + void renderOverlay(RenderEvent event) + { + if (!mIsInitialized) return; + + if (!mTextureCache.isLoaded) mTextureCache.load(); + + mCaption.show(); + mFadeInOut.fadeInOut((mActivity.isNone()) ? -0.1 : 0.2); + gb_Blur.setEnabled(mOptions.isBlurEnabled() && !mActivity.isNone()); + + double alpha = mFadeInOut.getAlpha(); + + if (mActivity.isNone() && alpha == 0.0) return; + + gb_ViewModel viewModel; + if (mActivity.isWeapons()) mWeaponMenu.fill(viewModel); + else if (mActivity.isInventory()) mInventoryMenu.fill(viewModel); + else if (mActivity.isSpecials()) mspecialsmenu.fill(viewModel); + else if (mActivity.isEquipment()) mEquipmenu.fill(viewModel); + + verifyViewModel(viewModel); + + gb_Dim.dim(alpha, mOptions); + int viewtype = mOptions.getViewType(); + switch(mActivity.getActivity()) + { + case mActivity.Weapons: + case mActivity.Inventory: break; + case mActivity.Specials: viewtype = VIEW_TYPE_WHEEL; break; + case mActivity.Equipments: viewtype = VIEW_TYPE_WHEEL; break; + } + + switch (viewtype) + { + case VIEW_TYPE_BLOCKY: + mBlockyView.setAlpha(alpha); + mBlockyView.setScale(mOptions.getScale()); + mBlockyView.setBaseColor(mOptions.getColor()); + mBlockyView.display(viewModel); + break; + + case VIEW_TYPE_WHEEL: + { + gb_WheelControllerModel controllerModel; + mWheelController.fill(controllerModel); + mWheelIndexer.update(viewModel, controllerModel); + int selectedViewIndex = mWheelIndexer.getSelectedIndex(); + + if (mActivity.isWeapons()) tickIf(mWeaponMenu.setSelectedIndexFromView(viewModel, selectedViewIndex)); + else if (mActivity.isInventory()) tickIf(mInventoryMenu.setSelectedIndex(selectedViewIndex)); + else if (mActivity.isSpecials()) tickIf(mspecialsmenu.setSelectedIndex(selectedViewIndex)); + else if (mactivity.isequipment()) tickIf(mEquipmenu.setSelectedIndex(selectedViewIndex)); + if (selectedViewIndex != -1) viewModel.selectedIndex = selectedViewIndex; + + int selectedIndex; + switch(mActivity.getActivity()) + { + case mActivity.Weapons: selectedIndex = mWeaponMenu.getSelectedIndex(); break; + case mActivity.Inventory: selectedIndex = mInventoryMenu.getSelectedIndex(); break; + case mActivity.Specials: selectedIndex = mspecialsmenu.getSelectedIndex(); break; + case mActivity.Equipments: selectedIndex = mEquipmenu.getSelectedIndex(); break; + } + + mWheelView.setAlpha(alpha); + mWheelView.setBaseColor(mOptions.getColor()); + mWheelView.setRotating(mActivity.isWeapons()); + + int innerIndex = mWheelIndexer.getInnerIndex(selectedIndex, viewModel); + int outerIndex = mWheelIndexer.getOuterIndex(selectedIndex, viewModel); + mWheelView.display( viewModel + , controllerModel + , mOptions.isMouseInWheel() + , innerIndex + , outerIndex + ); + break; + } + + case VIEW_TYPE_TEXT: + mTextView.setAlpha(alpha); + mTextView.setScale(mOptions.getTextScale()); + mTextView.display(viewModel); + break; + + } + } + +// private: //////////////////////////////////////////////////////////////////////////////////////// + + private play + bool isDisabledOnAutomap() const + { + return automapActive && !mOptions.isOnAutomap(); + } + + private play + bool isPlayerFrozen() const + { + return players[consolePlayer].isTotallyFrozen() && !mOptions.isFrozenCanOpen(); + } + + private ui void tickIf(bool mustTick) + { + if (mustTick) mSounds.playTick(); + } + + private ui void toggleWeapons() + { + if (mActivity.isWeapons()) close(); + else openWeapons(); + } + + private ui void toggleInventory() + { + if (mActivity.isInventory()) close(); + else openInventory(); + } + + private ui void toggleSpecials() + { + if(mActivity.isSpecials()) close(); + else openSpecials(); + } + + private ui void toggleEquipments() + { + if(mActivity.isEquipment()) close(); + else openEquipment(); + } + + private ui + void openWeapons() + { + if (gb_Player.isDead() || mWeaponMenu.isThereNoWeapons()) + { + mSounds.playNope(); + return; + } + + mWeaponMenu.setSelectedWeapon(gb_WeaponWatcher.current()); + mSounds.playOpen(); + mActivity.openWeapons(); + } + + private ui + void openInventory() + { + if (gb_Player.isDead() || gb_InventoryMenu.thereAreNoItems()) + { + mSounds.playNope(); + return; + } + + mSounds.playOpen(); + mActivity.openInventory(); + } + + private ui void openSpecials() + { + if (gb_Player.isDead() || playercantspecial()) + { + mSounds.playNope(); + return; + } + + mspecialsmenu.getspecials(players[consolePlayer].readyweapon);//(gb_WeaponWatcher.current()); + if(mspecialsmenu.thereAreNoSpecials()) + { + + //mSounds.playNope(); + gb_Sender.sendGiveItemEvent("GoWeaponSpecialAbility"); //no wheel, instead go normal + return; + } + + mSounds.playOpen(); + mActivity.openSpecials(); + } + + private ui void openEquipment() + { + if (gb_Player.isDead() || mEquipmenu.noequipments()) + { + mSounds.playNope(); + return; + } + + mSounds.playOpen(); + mActivity.openEquipment(); + } + + private clearscope void close() + { + mSounds.playClose(); + mActivity.close(); + } + + private ui bool playercantspecial() + { + return (players[consoleplayer].mo.findinventory("CantWeaponSpecial")); + } + + private ui void confirmSelection() + { + switch(mActivity.getactivity()) + { + case gb_activity.Weapons: + gb_Sender.sendSelectEvent(mWeaponMenu.confirmSelection()); break; + case gb_activity.Inventory: + gb_Sender.sendUseItemEvent(mInventoryMenu.confirmSelection()); break; + case gb_activity.Specials: + gb_Sender.sendGiveItemEvent("GoWeaponSpecialAbility"); //AAAA i forgot this, and was wondering why this wasnt working + gb_Sender.sendGiveItemEvent(mspecialsmenu.ConfirmSelection()); break; + case gb_activity.Equipments: + gb_Sender.sendGiveItemEvent("ToggleEquipment"); + gb_Sender.sendGiveItemEvent(mEquipmenu.ConfirmSelection()); break; + } + } + + private ui + void rotateWeaponPriority() + { + if (mActivity.isWeapons()) + { + gb_CustomWeaponOrderStorage.savePriorityRotation(mWeaponSetHash, mWeaponMenu.getSelectedIndex()); + mWeaponMenu.rotatePriority(); + } + } + + private ui + void rotateWeaponSlot() + { + if (mActivity.isWeapons()) + { + gb_CustomWeaponOrderStorage.saveSlotRotation(mWeaponSetHash, mWeaponMenu.getSelectedIndex()); + mWeaponMenu.rotateSlot(); + } + } + + private + void resetCustomOrder() + { + gb_CustomWeaponOrderStorage.reset(mWeaponSetHash); + gb_WeaponData weaponData; + gb_WeaponDataLoader.load(weaponData); + mWeaponMenu = gb_WeaponMenu.from(weaponData, mOptions); + } + + private ui + void verifyViewModel(gb_ViewModel model) + { + uint nTags = model.tags .size(); + uint nSlots = model.slots .size(); + uint nIndices = model.indices .size(); + uint nIcons = model.icons .size(); + uint nIconScaleXs = model.iconScaleXs .size(); + uint nIconScaleYs = model.iconScaleYs .size(); + uint nQuantities1 = model.quantity1 .size(); + uint nQuantitiesMax1 = model.maxQuantity1.size(); + uint nQuantities2 = model.quantity2 .size(); + uint nQuantitiesMax2 = model.maxQuantity2.size(); + + if (nTags > 0 + && (model.selectedIndex >= nTags + || nTags != nSlots + || nTags != nIndices + || nTags != nIcons + || nTags != nIconScaleXs + || nTags != nIconScaleYs + || nTags != nQuantities1 + || nTags != nQuantitiesMax1 + || nTags != nQuantities2 + || nTags != nQuantitiesMax2)) + { + Console.printf("Bad view model:\n" + "selected index: %d,\n" + "tags: %d,\n" + "slots: %d,\n" + "indices: %d,\n" + "icons: %d,\n" + "icon scale X: %d,\n" + "icon scale Y: %d,\n" + "quantities 1: %d,\n" + "max quantities 1: %d,\n" + "quantities 2: %d,\n" + "max quantities 2: %d,\n" + , model.selectedIndex + , nTags + , nSlots + , nIndices + , nIcons + , nIconScaleXs + , nIconScaleYs + , nQuantities1 + , nQuantitiesMax1 + , nQuantities2 + , nQuantitiesMax2 + ); + } + } + + bool isOpened() //ig this could work to indicate when the wheel is opened, just need a pointer to the handler + { + if(!mIsInitialized) //it cant be opened if its not initialized + return false; + return !mActivity.isNone(); + } + + enum ViewTypes + { + VIEW_TYPE_BLOCKY = 0, + VIEW_TYPE_WHEEL = 1, + VIEW_TYPE_TEXT = 2, + } + + private + void initialize() + { + mOptions = gb_Options.from(); + mFontSelector = gb_FontSelector.from(); + mSounds = gb_Sounds.from(mOptions); + + gb_WeaponData weaponData; + gb_WeaponDataLoader.load(weaponData); + mWeaponSetHash = gb_CustomWeaponOrderStorage.calculateHash(weaponData); + mWeaponMenu = gb_WeaponMenu.from(weaponData, mOptions); + gb_CustomWeaponOrderStorage.applyOperations(mWeaponSetHash, mWeaponMenu); + mInventoryMenu = gb_InventoryMenu.from(); + mSpecialsmenu = gb_specialsmenu.from(); + mEquipmenu = gb_equipmentmenu.from(); + + mActivity = gb_Activity.from(); + mFadeInOut = gb_FadeInOut.from(); + mFreezer = gb_Freezer.from(mOptions); + + mTextureCache = gb_TextureCache.from(); + mCaption = gb_Caption.from(); + mInventoryUser = gb_InventoryUser.from(); + mChanger = gb_Changer.from(mCaption, mOptions, mInventoryUser); + mNeteventProcessor = gb_NeteventProcessor.from(mChanger); + + mBlockyView = gb_BlockyView.from(mTextureCache, mOptions, mFontSelector); + mTextView = gb_TextView.from(mOptions, mFontSelector); + + mMultiWheelMode = gb_MultiWheelMode.from(mOptions); + let screen = gb_Screen.from(mOptions); + mWheelView = gb_WheelView.from( mOptions + , mMultiWheelMode + , mTextureCache + , screen + , mFontSelector + ); + mWheelController = gb_WheelController.from(mOptions, screen); + mWheelIndexer = gb_WheelIndexer.from(mMultiWheelMode, screen); + + mIsInitialized = true; + } + + private gb_Options mOptions; + private gb_FontSelector mFontSelector; + private gb_Sounds mSounds; + + private string mWeaponSetHash; + private gb_WeaponMenu mWeaponMenu; + private gb_InventoryMenu mInventoryMenu; + private gb_specialsmenu mspecialsmenu; //pb specials thing + private gb_equipmentmenu mEquipmenu; //pb equipments thing + private gb_Activity mActivity; + private gb_FadeInOut mFadeInOut; + private gb_Freezer mFreezer; + + private gb_TextureCache mTextureCache; + private gb_Caption mCaption; + private gb_InventoryUser mInventoryUser; + private gb_Changer mChanger; + private gb_NeteventProcessor mNeteventProcessor; + + private gb_BlockyView mBlockyView; + private gb_TextView mTextView; + + private gb_MultiWheelMode mMultiWheelMode; + private gb_WheelView mWheelView; + private gb_WheelController mWheelController; + private gb_WheelIndexer mWheelIndexer; + + private bool mIsInitialized; + +} // class gb_EventHandler diff --git a/zscript/gearbox/event_processor.zs b/zscript/gearbox/event_processor.zs new file mode 100644 index 0000000000..eb23805ea1 --- /dev/null +++ b/zscript/gearbox/event_processor.zs @@ -0,0 +1,40 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2020-2021 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +class gb_EventProcessor +{ + + static + int process(ConsoleEvent event, bool isSelectOnKeyUp) + { + if (event.name == "gb_toggle_weapon_menu") return InputToggleWeaponMenu; + if (event.name == "gb_toggle_weapon_menu_up" && isSelectOnKeyUp) return InputConfirmSelection; + + if (event.name == "gb_toggle_inventory_menu") return InputToggleInventoryMenu; + if (event.name == "gb_toggle_inventory_menu_up" && isSelectOnKeyUp) return InputConfirmSelection; + if (event.name == "gb_rotate_weapon_priority") return InputRotateWeaponPriority; + if (event.name == "gb_rotate_weapon_slot" ) return InputRotateWeaponSlot; + + if (event.name == "pb_special_wheel") return InputToggleSpecialMenu; + if (event.name == "pb_special_wheel_up" && isSelectOnKeyUp) return InputConfirmSelection; + if (event.name == "pb_equip_wheel") return InputToggleEquipMenu; + if (event.name == "pb_equip_wheel_up" && isSelectOnKeyUp) return InputConfirmSelection; + + return InputNothing; + } + +} // class gb_EventProcessor diff --git a/zscript/gearbox/font_selector.zs b/zscript/gearbox/font_selector.zs new file mode 100644 index 0000000000..fa10c6d978 --- /dev/null +++ b/zscript/gearbox/font_selector.zs @@ -0,0 +1,53 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2021 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +class gb_FontSelector +{ + + static + gb_FontSelector from() + { + let result = new("gb_FontSelector"); + result.mFontString = gb_Cvar.from("gb_font"); + return result; + } + + Font getFont() + { + Font newFont = Font.getFont(mFontString.getString()); + + if (newFont == NULL && mOldFont != NULL) + { + Console.printf(StringTable.localize("$GB_BAD_FONT"), mFontString.getString()); + } + + mOldFont = newFont; + + if (newFont == NULL) + { + newFont = NewSmallFont; + } + + return newFont; + } + +// private: //////////////////////////////////////////////////////////////////////////////////////// + + + private gb_Cvar mFontString; + private transient Font mOldFont; +} diff --git a/zscript/gearbox/freezer.zs b/zscript/gearbox/freezer.zs new file mode 100644 index 0000000000..a6383a0d58 --- /dev/null +++ b/zscript/gearbox/freezer.zs @@ -0,0 +1,129 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2020-2021 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +class gb_Freezer play +{ + + static + gb_Freezer from(gb_Options options) + { + let result = new("gb_Freezer"); + result.mWasFrozen = false; + result.mOptions = options; + result.mWasLevelFrozen = false; + return result; + } + + void freeze() + { + if (mWasFrozen) return; + mWasFrozen = true; + + int freezeMode = mOptions.getTimeFreezeMode(); + if (isLevelFreezeEnabled (freezeMode)) freezeLevel(); + if (isPlayerFreezeEnabled(freezeMode)) freezePlayer(); + } + + void thaw() + { + if (!mWasFrozen) return; + mWasFrozen = false; + + if (isLevelThawEnabled()) thawLevel(); + thawPlayer(); + } + +// private: //////////////////////////////////////////////////////////////////////////////////////// + + /** + * Corresponds to gb_FreezeValues in menudef. + */ + private static + bool isLevelFreezeEnabled(int freezeMode) + { + return !multiplayer && (freezeMode == 1); + } + + /** + * Corresponds to gb_FreezeValues in menudef. + * + * Freezing level without player causes weird behavior, like weapon bobbing + * while Gearbox is open. So, freeze player when level is frozen too. + */ + private static + bool isPlayerFreezeEnabled(int freezeMode) + { + return freezeMode != 0; + } + + /** + * Thaw regardless of freeze mode. + */ + private static + bool isLevelThawEnabled() + { + return !multiplayer; + } + + private + void freezeLevel() + { + mWasLevelFrozen = level.isFrozen(); + level.setFrozen(true); + } + + private + void freezePlayer() + { + mWasPlayerFrozen = true; + + PlayerInfo player = players[consolePlayer]; + + mCheats = player.cheats; + mVelocity = player.mo.vel; + mGravity = player.mo.gravity; + + gb_Sender.sendFreezePlayerEvent(player.cheats | FROZEN_CHEATS_FLAGS, (0, 0, 0), 0); + } + + private + void thawLevel() const + { + level.setFrozen(mWasLevelFrozen); + } + + private + void thawPlayer() const + { + if (mWasPlayerFrozen) gb_Sender.sendFreezePlayerEvent(mCheats, mVelocity, mGravity); + mWasPlayerFrozen = false; + } + + const FROZEN_CHEATS_FLAGS = CF_TotallyFrozen | CF_Frozen; + + private bool mWasFrozen; + + private bool mWasLevelFrozen; + private bool mWasPlayerFrozen; + + private int mCheats; + private vector3 mVelocity; // to reset weapon bobbing. + private double mGravity; + + private gb_Options mOptions; + +} // class gb_Freezer diff --git a/zscript/gearbox/gearboxInfo/Readme.gearbox b/zscript/gearbox/gearboxInfo/Readme.gearbox new file mode 100644 index 0000000000..f5c910b4d1 --- /dev/null +++ b/zscript/gearbox/gearboxInfo/Readme.gearbox @@ -0,0 +1,83 @@ +# Gearbox + +Gearbox is an add-on for GZDoom engine that provides more convenient ways to +select weapons and items. + +This mod is a part of [m8f's toolbox](https://mmaulwurff.github.io/pages/toolbox). + +![Blocks with Treasure Tech](screenshots/blocks-treasure-tech.png) + +(Blocks with [Treasure Tech](https://forum.zdoom.org/viewtopic.php?f=43&t=66995)) + +[![Wheel with Heretic](screenshots/inventory-wheel-heretic.png)](https://youtu.be/WXZUBtbsY7Y) + +(Wheel with Heretic) + +## How to Use + +GZDoom 4.5 required. + +1. open the menu by assigned key, or by next/previous weapon keys, if enabled in + options +2. select the weapon with next/previous weapon keys, or with mouse (wheel only) + +## Features + +- Different representations: blocks, wheel, plain text +- Press Fire key to select and Alt Fire key to cancel +- Color and scale options +- Behavior options +- Multiplayer compatible +- Reaction to number keys +- extras.wad icon support for vanilla weapons +- Inventory item selection + +## Planned + +- Patches for weapon icon adjustments +- More representations +- Moving weapon between slots and changing order + +## Note for Weapon Mod Authors + +If you want Gearbox to support your mod out of the box, assign +Inventory.AltHudIcon for your weapons! Tag property is also nice to have. + +## Compatibility Issues + +- [PyWeaponWheel v0.3](https://forum.zdoom.org/viewtopic.php?f=43&t=61061) + overrides time freezing. If you are using both mods and want to freeze time + with Gearbox, set PyWeaponWheel's option "Freeze when wheel is open" + (`py_weaponwheel_freeze` CVar) to Off. + + Note that PyWeaponWheel may be built in some mods, for example in Project + Brutality. The solution is the same: disable time PyWeaponWheel's time + freezing. + +## Known Issues + +- Weapon icons in wheel aren't affected by "HUD preserves aspect ration" option. +- Mouse input in wheel in multiplayer causes screen shake. + +## License + +- code: [GPLv3](copying.txt) + +## Acknowledgments + +- Thanks to kadu522 for general help and support. +- Blocky view is designed to resemble the weapon menu from Half-Life by Valve. +- Thanks to Marrub for [ZScriptDoc](https://github.com/marrub--/zdoom-doc). +- Thanks to Talon1024 for help with time freezing option. +- Thanks to Player701 for help with key event processing code. +- Thanks to KeksDose for a concept of VM abort handler. +- Thanks to DrPyspy for allowing to use mouse input code from PyWeaponWheel. +- Thanks to Carrascado for bug fixes and new features. +- Thanks to Accensus, Proydoha, mamaluigisbagel, TheRailgunner, Captain J, + Enjay, StroggVorbis, krutomisi, Cutmanmike, StraightWhiteMan, JohnDoe8, HDV, + Zhs2 and Apollucas for feature suggestions. +- Thanks to Accensus, Proydoha, mamaluigisbagel, Ac!d, wildweasel, + Dark-Assassin, rparhkdtp, Samarai1000, Mr. Blazkowicz, lucker42, spectrefps, + Someone64, Lippeth, JMartinez9820, generic name guy and sebastianpanetta for + bug reports. +- See also [credits list](credits.md). diff --git a/zscript/gearbox/gearboxInfo/changelog.gearbox b/zscript/gearbox/gearboxInfo/changelog.gearbox new file mode 100644 index 0000000000..7365f62a9b --- /dev/null +++ b/zscript/gearbox/gearboxInfo/changelog.gearbox @@ -0,0 +1,333 @@ + +v0.7.2 +- 2022-07-13 don't open weapon menu if there is no weapons +- 2022-07-13 fix text out of weapon blocks +- 2022-07-13 replace ellipsis with three dots for fonts that don't have ellipsis +- 2022-07-13 Merge remote-tracking branch 'origin/master' +- 2022-07-13 Feature/option select first slot weapon (#32) +- 2022-07-12 update copyright notice +- (Carrascado's/master) 2022-07-12 Feature: option to reverse slot cycle order (#31) +- 2022-07-12 update copyright notice and Readme +- 2022-07-12 add audio feedback when menu cannot be opened +- 2022-07-11 I fix a bug by which the inventory could be "invisibly" opened and closed when there were no items +- 2022-07-12 Merge pull request #29 from Carrascado/feature/turn-toggle-sound-into-open-and-close +- (Carrascado's/feature/turn-toggle-sound-into-open-and-close, feature/turn-toggle-sound-into-open-and-close) 2022-07-11 Now there is a code-wise difference between the open and close menu sounds +- 2022-07-11 Fix #28: toggle and tick sounds played at the same time + +v0.7.1 +- 2022-06-26 fix some spelling +- 2022-06-26 delete scripts (moved to a separate repository) +- (tag: v0.7.1-b) 2022-06-26 fix a crash on Delta Touch with GLES 3.2 caused by blur shader +- 2021-11-07 add menu command to copy custom player color +- 2021-10-23 make custom weapon order keys descriptions more clear +- 2021-10-01 fix gitignore +- 2021-08-28 search for textures more precisely + +v0.7.0 +- 2021-08-28 don't scale weapons with no icons in wheel +- 2021-08-28 prevent weapon sway in wheel +- 2021-08-28 make Zabor shorter +- 2021-08-28 fix changing weapon order +- 2021-08-22 fix keyconf lump +- 2021-08-22 adapt to inverted mouse settings +- 2021-08-22 organize menus +- 2021-08-22 fix #15: VM abort in inventory menu +- 2021-08-22 more readable zabor +- 2021-08-22 synchronize cvar lists +- 2021-08-21 add resetting custom weapon order +- 2021-08-18 add key to change weapon slot +- 2021-08-15 add key to change weapon priority +- 2021-08-13 add font selection +- 2021-08-12 update Readme again +- 2021-08-12 update Readme + +v0.6.0 +- 2021-08-12 fix VM abort in text view when no weapons +- 2021-08-12 add plain text view +- 2021-08-10 fix #12: take into account weapon scaling properties for icons +- 2021-08-10 fix #11: SWWM GZ - two Explodium guns are selectable +- 2021-08-10 fix #10: velocity after freezing was restored even if freezing is off +- 2021-08-10 fix #8: unblock mouse input in Blocks view +- 2021-07-06 update build script +- 2021-04-14 add more null checks + +v0.5.1 +- 2021-04-13 fix menudef code style +- 2021-04-13 scripts cleanup +- 2021-04-13 check spelling +- 2021-04-13 add space after menu titles +- 2021-04-13 fix using inventory items when player is frozen +- 2021-04-11 fix wheel hands length + +v0.5.0 +- 2021-04-11 fix truncation of floating point warning +- 2021-04-11 update Zabor, and an option to disable it +- 2021-04-11 Revert "wheel: draw weapon tag if it has no icon" +- 2021-04-10 add wheel scale +- 2021-04-07 separate Screen class from blocks view, move to wheel +- 2021-04-07 remove redundant menu option +- 2021-04-07 move text drawing class to wheel +- 2021-04-07 display weapon name tags on change with native GZDoom function +- 2021-04-04 fix gravity after closing gearbox +- 2021-04-04 wheel: draw weapon tag if it has no icon +- 2021-04-03 add compatibility with Event Handler Destroyer +- 2021-04-01 add Gearbox options menu to simplified options menu +- 2021-03-26 don't call statusbar instance for static getInventoryIcon function +- 2021-03-25 fix height when frozen in Heretic water +- 2021-03-24 move networking to sender +- 2021-03-24 add icon to options menu +- 2021-03-20 rework player and enemies freezing +- 2021-03-20 add KeksDose to Acknowledgments +- 2021-03-07 new input +- 2021-03-12 update VM abort handler +- 2021-03-03 fix blocks view positioning + +v0.4.1 +- 2021-03-02 update Readme +- 2021-03-02 hardcode icons for Smooth Doom +- 2021-03-02 prevent vm abort when no inventory items +- 2021-03-02 fix selecting hidden weapons +- 2021-03-02 make previous weapon carry over to the next level +- 2021-03-01 prevent blocks inventory view from aborting VM when there are no items +- 2021-03-01 don't draw placeholder icon in blocks view +- 2021-03-01 increase max multiwheel threshold, draw selected weapon on top +- 2021-03-01 add note about keys to Readme +- 2021-03-01 fix constant ticking on tomed weapons +- 2021-03-01 fix vm abort when the last inventory item is used up +- 2021-03-01 fix misaligned quantity dots when quantity is odd +- 2021-02-28 fix spelling, add missing cvars to zabor and gb_reset +- 2021-02-28 fix truncation of floating point values + +v0.4 +- 2021-02-28 update Readme +- 2021-02-28 better handling of tall weapon icons +- 2021-02-28 minor naming refactoring (still a lot of names to go...) +- 2021-02-28 don't rotate items in wheel, respect preserve aspect ration setting +- 2021-02-28 minor refactoring +- 2021-02-28 naming refactoring +- 2021-02-27 add an option to set if frozen player can open Gearbox +- 2021-02-27 move UI options to a submenu +- 2021-02-27 add reset to defaults command +- 2021-02-27 make position configurable for blocks view +- 2021-02-27 add blur option +- 2021-02-27 code style +- 2021-02-27 options refactoring +- 2021-02-25 add a command for manual report +- 2021-02-24 add cvars dump to vm event handler +- 2021-02-24 add vm abort handler +- 2021-02-23 add select previous weapon key from m_gizmos +- 2021-02-23 fix complementary colors in blocks view +- 2021-02-21 fix division by zero when max ammo is 0 +- 2021-02-20 don't draw 10 pips if ammo or number of items is less than 10 +- 2021-02-20 wheel: don't move weapon description if not necessary +- 2021-02-19 fix repeated ticking when player has no weapons +- 2021-02-19 don't show amount for items with max amount 1 +- 2021-02-19 add menudef entry for inventory menu key +- 2021-02-18 allow slot keys in inventory menu +- 2021-02-18 add tick sound to inventory menu +- 2021-02-18 fix next/previous buttons in inventory menu +- 2021-02-15 barebones inventory menu +- 2021-02-16 update screenshots, update Readme + +v0.3.2 +- 2021-02-16 increase minimal angle between wheel hands for clarity +- 2021-02-16 make wheel hands transparent in the middle +- 2021-02-16 draw the selected thing in the second wheel on top of other things +- 2021-02-16 remove a message for obsolete key bind +- 2021-02-16 fix out of bounds angle on the second wheel +- 2021-02-15 limit minimal angle between wheel hands +- 2021-02-15 let Gearbox know the current selected weapon + +v0.3.1 +- 2021-02-14 add missing people to Acknowledgments +- 2021-02-14 fix spelling +- 2021-02-14 menu cleanup +- 2021-02-14 make sound effects optional +- 2021-02-14 wheel: don't move weapon description if not in multiwheel mode +- 2021-02-14 disable Gearbox in intermission screen +- 2021-02-13 remove partial LZDoom compatibility + +v0.3 +- 2021-02-13 fix VM abort when no weapons, AGAIN +- 2021-02-13 update Readme +- 2021-02-13 smoother text background image +- 2021-02-13 make lower limit for text box width +- 2021-02-13 add optional locked position mode +- 2021-02-13 more distinct elements color for black or white blocks view +- 2021-02-13 make displaying weapon tags on weapon change respect GZDoom option +- 2021-02-13 fix drawing text box on top of wheel when the screen is scaled +- 2021-02-13 add an option to display weapon tags +- 2021-02-13 unify fonts, remove font selection +- 2021-02-13 add weapon tags to blocks view +- 2021-02-13 add weapon tag display after changing weapons +- 2021-02-12 remove one parameter from text box drawing function +- 2021-02-12 move drawing text into a separate file +- 2021-02-12 micro cleanup +- 2021-02-12 move some display-related files into a directory +- 2021-02-12 move texture cache out of wheel view, cache blocks view textures +- 2021-02-12 more text box drawing refactoring +- 2021-02-11 fix text box color and increase background image resolution +- 2021-02-11 refactor drawing weapon description +- 2021-02-11 add weapon tag display to wheel +- 2021-02-05 fix blocks view index +- 2021-02-02 add open/close sound +- 2021-02-01 add tick sound +- 2021-01-29 allow catching more than 2 key bindings +- 2021-01-26 add a note regarding PyWeaponWheel +- 2021-01-26 cache view model +- 2021-01-26 store ammo pip size in cache +- 2021-01-26 cache scale factor in wheel +- 2021-01-26 add texture cache to wheel. performance gain is non-existent +- 2021-01-25 fix warnings, combine coordinate operations into vector operations +- 2021-01-24 implement ammo display on wheel +- 2021-01-23 make disabling Gearbox on automap optional +- 2021-01-23 fix overriding icons from invalid ones from service +- 2021-01-22 LZDoom half-compatibility: only Blocks view +- 2021-01-21 update Readme +- 2021-01-21 add HideService to show DualExplodiumGun from SWWM GZ only when appropriate +- 2021-01-21 implement icon patches via Service +- 2021-01-21 Merge branch 'master' into mpatch +- 2021-01-20 more consistent selection in multiwheel +- 2021-01-20 multiwheel: click on slot selects the first weapon in this slot +- 2021-01-19 add patches with icon support +- 2021-01-19 update Readme +- 2021-01-19 fix bug with not selecting slot on slot key +- 2021-01-19 add an option to open menu on slot key +- 2021-01-19 move slot input check to a function +- 2021-01-18 don't open gearbox in automap +- 2021-01-18 fix file conflict with other mods +- 2021-01-17 add screen dim color option +- 2021-01-17 fix VM abort on no weapons again +- 2021-01-17 tweak wheel hands graphics +- 2021-01-17 remove unnecessary hiding of wheel elements on alpha != 1 +- 2021-01-17 fix weapon selection snapping to selected weapon on fade out +- 2021-01-17 close weapon selection by altfire +- 2021-01-16 add smooth wheel graphics by Accensus + +v0.2.2 +- 2021-01-13 add gradient to wheel background +- 2021-01-13 fix spelling +- 2021-01-13 add multiwheel engagement weapon number slider +- 2021-01-13 refactoring: move wheel/multiwheel mode decision to a separate class +- 2021-01-12 prevent opening gearbox while dead +- 2021-01-12 more reliable freezing while menu is opened +- 2021-01-11 fix VM abort in hubs +- 2021-01-09 add note to weapon mod authors to Readme +- 2021-01-07 remove obsolete comment + +v0.2.1 +- 2021-01-07 add time freezing +- 2021-01-07 fix multiplayer synchronization +- 2021-01-07 fix reaction to slot keys in some circumstances +- 2021-01-07 fix selecting a weapon if it's only one in slot +- 2021-01-06 add protection against the case when player has no weapons at all +- 2021-01-06 make colored tint on wheel weapons optional +- 2021-01-06 add an option to select font for wheel slots +- 2021-01-06 add little notches on sides of wheel selection bars +- 2021-01-06 add a link to toolbox page to Readme +- 2021-01-06 update Readme + +v0.2 +- 2021-01-06 reorganize option menu +- 2021-01-06 add an option for wheel horizontal position +- 2021-01-06 fix preventing cheat code input in some circumstances +- 2021-01-06 fix possible vm abort when dropping weapons +- 2021-01-06 fix scaling for blocks view +- 2021-01-06 fix scaling wheel pointer +- 2021-01-06 fix wheel weapon selection scaling +- 2021-01-06 fix slot label scaling +- 2021-01-06 remove hardcoded radius +- 2021-01-06 react to scaling changes +- 2021-01-06 fix VM abort due to computational error +- 2021-01-06 add screenshot with multiwheel +- 2021-01-06 fix VM abort with QCDE +- 2021-01-05 restore indexing the wheel by keys +- 2021-01-05 multiwheel: indexer works fine +- 2021-01-05 prettier multiwheel +- 2021-01-05 wip multiwheel more +- 2021-01-05 optimize graphics +- 2021-01-04 wip multiwheel +- 2021-01-04 don't let wheel pointer get from screen bounds +- 2021-01-04 fix VM abort with blocks view when there is only one weapon +- 2021-01-04 reset wheel pointer on number keys +- 2021-01-04 fix VM abort with QCDE +- 2021-01-04 don't reset wheel state on unrelated input keys +- 2021-01-04 update Readme +- 2021-01-04 fix bug with cloning the wheel twice +- 2021-01-04 minor wheel view cleanup +- 2021-01-04 add changelog script +- 2021-01-04 update Readme +- 2021-01-04 add an option to immediately select weapon if it's only one in slot +- 2021-01-04 add reaction to number keys +- 2021-01-04 move screen dimming out of the wheel view and make it optional +- 2021-01-04 minor wheel view cleanup +- 2021-01-04 update Readme +- 2021-01-04 add an option to select the weapon on key release +- 2021-01-04 implement mouse sensitivity sliders +- 2021-01-04 code style +- 2021-01-04 minor cleanup +- 2021-01-04 don't show ammo2 view if it's the same as ammo1 +- 2021-01-03 add an option to disable mouse input in weapon wheel +- 2021-01-03 bump required GZDoom version +- 2021-01-03 update Readme once more + +v0.1 +- 2021-01-03 update Readme +- 2021-01-03 update Readme, add screenshots +- 2021-01-03 better reaction to opening menu of prev/next weapon key +- 2021-01-03 make weapon wheel selection distinguishable when it's two weapons +- 2021-01-03 add an option to open menu on next/previous weapon key press +- 2021-01-03 update copyright year +- 2021-01-03 move options access to a separate file +- 2021-01-03 fix compatibility with Treasure Tech +- 2021-01-03 fix VM abort on titlemaps +- 2021-01-03 enable switching between views +- 2021-01-03 minor refactoring +- 2021-01-03 get rid of buggy wheel selection interpolation +- 2021-01-03 rotate tall weapons so they fit in the weapon wheel +- 2021-01-03 fix wrong index in weapon wheel +- 2021-01-02 stop capturing mouse events after selecting weapons +- 2021-01-02 move some files to directories +- 2020-12-09 add a separate flag variable for event handler initialization state +- 2020-12-09 refactor weapon watcher +- 2020-12-09 squash script warnings +- 2020-12-09 refactor level loading state into a function +- 2020-12-09 refactor Activity class +- 2020-11-23 implement weapon wheel selection interpolation +- 2020-11-23 implement weapon wheel indexer +- 2020-11-23 add visible weapon selection to weapon wheel +- 2020-10-18 add weapon wheel +- 2020-10-15 add Marrub to Acknowledgments +- 2020-10-15 remove unused stuff from Blocky view +- 2020-10-15 add color setting +- 2020-10-15 rename HL weapon view to Blocky View +- 2020-10-14 add scaling +- 2020-10-13 HL view refactoring +- 2020-10-13 add placeholder weapon icon +- 2020-10-11 add fade in-out +- 2020-10-11 made view static +- 2020-10-11 add automatic scaling if weapon doesn't fit into the box in HL view +- 2020-10-11 add scrolling weapons back +- 2020-10-11 add ammo2 display +- 2020-10-11 add selected slot drawing +- 2020-10-11 add basic Half-Life-like weapon menu (WIP) +- 2020-10-11 fix view model +- 2020-10-10 fix weapon order +- 2020-10-10 add known words list for code spellcheck +- 2020-10-10 add view model filling +- 2020-10-09 add view model +- 2020-10-09 implement selection confirmation +- 2020-10-09 propagate changes through network +- 2020-10-08 add keysection +- 2020-10-08 add toggling weapon menu +- 2020-10-08 add input processor +- 2020-10-08 add command and selecting next weapon logic +- 2020-10-08 add weapon watcher +- 2020-10-08 add weapon menu state +- 2020-10-08 weapon data loading algorithm tweak +- 2020-10-08 add code spelling script +- 2020-10-07 add weapon data loader to event handler +- 2020-10-07 add weapon data loader +- 2020-10-07 add license +- 2020-10-07 add build script +- 2020-10-06 add Readme diff --git a/zscript/gearbox/gearboxInfo/copying.gearbox b/zscript/gearbox/gearboxInfo/copying.gearbox new file mode 100644 index 0000000000..f288702d2f --- /dev/null +++ b/zscript/gearbox/gearboxInfo/copying.gearbox @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + 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, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/zscript/gearbox/gearboxInfo/credits.gearbox b/zscript/gearbox/gearboxInfo/credits.gearbox new file mode 100644 index 0000000000..f1cfb0e1c3 --- /dev/null +++ b/zscript/gearbox/gearboxInfo/credits.gearbox @@ -0,0 +1,16 @@ +# Gearbox Credits + +- all graphics are under [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/). +- gb_circ.png, gb_hand.png, gb_hcir.png and gb_pntr.png are made by Accensus with minor edits by m8f. +- gb_tick.ogg is converted from + [384187__malle99__click-tick.wav](https://freesound.org/people/malle99/sounds/384187/) + by malle99. [Creative Commons 0 License](http://creativecommons.org/publicdomain/zero/1.0/) +- gb_toggle.ogg is converted from + [362625__kermite607__metal-clang-sound.wav](https://freesound.org/people/kermite607/sounds/362625/) + by kermite607. [Creative Commons 0 License.](http://creativecommons.org/publicdomain/zero/1.0/) +- gb_nope.ogg is [Switch.ogg](https://freesound.org/people/egomassive/sounds/536802/) by egomassive [Creative Commons 0 License.](http://creativecommons.org/publicdomain/zero/1.0/) +- shaders/mfx_bss_blur.fp is made by [Marisa + Kirisame](https://github.com/OrdinaryMagician/), taken from + [MariFX](https://github.com/OrdinaryMagician/marifx_m) (GPLv3), modified by m8f. +- Wheel mouse input code is based on code from PyWeaponWheel by DrPyspy. +- [MD5 Implementation in ZScript](https://github.com/3saster/GZDoom_Utilities) by 3saster (see [MD5.zs](zscript/MD5/MD5.zs) for license). diff --git a/zscript/gearbox/input.zs b/zscript/gearbox/input.zs new file mode 100644 index 0000000000..9a76669bcb --- /dev/null +++ b/zscript/gearbox/input.zs @@ -0,0 +1,58 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2020-2021 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +enum gb_Inputs +{ + + InputNothing, + + InputSelectNextWeapon, + InputSelectPrevWeapon, + InputConfirmSelection, + InputToggleWeaponMenu, + + InputSelectSlotBegin, + InputSelectSlotEnd = InputSelectSlotBegin + 11, + InputClose, + + InputToggleInventoryMenu, + InputConfirmInventorySelection, + + InputRotateWeaponPriority, + InputRotateWeaponSlot, + InputResetCustomOrder, + + InputToggleSpecialMenu, + InputToggleEquipMenu, +} // enum gb_Inputs + +class gb_Input +{ + + static + bool isSlot(gb_Inputs input) + { + return (InputSelectSlotBegin <= input && input <= InputSelectSlotEnd); + } + + static + int getSlot(gb_Inputs input) + { + return input - InputSelectSlotBegin; + } + +} // class gb_Input diff --git a/zscript/gearbox/input_processor.zs b/zscript/gearbox/input_processor.zs new file mode 100644 index 0000000000..d70fc0dc56 --- /dev/null +++ b/zscript/gearbox/input_processor.zs @@ -0,0 +1,55 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2020-2021 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +class gb_InputProcessor +{ + + static + gb_Inputs process(InputEvent event) + { + if (event.type != InputEvent.Type_KeyDown) return InputNothing; + + int key = event.keyScan; + if (isKeyForCommand(key, "weapNext" )) return InputSelectNextWeapon; + if (isKeyForCommand(key, "weapPrev" )) return InputSelectPrevWeapon; + if (isKeyForCommand(key, "+attack" )) return InputConfirmSelection; + if (isKeyForCommand(key, "+altAttack")) return InputClose; + + for (int i = 0; i <= 11; ++i) + { + if (isKeyForCommand(key, string.format("slot %d", i))) return i + InputSelectSlotBegin; + } + + return InputNothing; + } + +// private: //////////////////////////////////////////////////////////////////////////////////////// + + private static + bool isKeyForCommand(int key, string command) + { + Array keys; + bindings.getAllKeysForCommand(keys, command); + uint nKeys = keys.size(); + for (uint i = 0; i < nKeys; ++i) + { + if (keys[i] == key) return true; + } + return false; + } + +} // class gb_InputProcessor diff --git a/zscript/gearbox/inventory_menu.zs b/zscript/gearbox/inventory_menu.zs new file mode 100644 index 0000000000..e880162b64 --- /dev/null +++ b/zscript/gearbox/inventory_menu.zs @@ -0,0 +1,143 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2021 + * Carrascado 2022 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +class gb_InventoryMenu +{ + + static + gb_InventoryMenu from() + { + let result = new("gb_InventoryMenu"); + + result.mSelectedIndex = 0; + + return result; + } + + static + bool thereAreNoItems() + { + return getItemsNumber() == 0; + } + + string confirmSelection() const + { + let item = players[consolePlayer].mo.inv; + int index = 0; + while (item != NULL) + { + if (item.bInvBar) + { + if (index == mSelectedIndex) return item.getClassName(); + ++index; + } + item = item.inv; + } + + return ""; + } + + ui + bool selectNext() + { + int nItems = getItemsNumber(); + if (nItems == 0) return false; + + mSelectedIndex = (mSelectedIndex + 1) % nItems; + + return true; + } + + ui + bool selectPrev() + { + int nItems = getItemsNumber(); + if (nItems == 0) return false; + + mSelectedIndex = (mSelectedIndex - 1 + nItems) % nItems; + + return true; + } + + ui + bool setSelectedIndex(int index) + { + if (index == -1 || mSelectedIndex == index) return false; + + mSelectedIndex = index; + + return true; + } + + ui + int getSelectedIndex() const + { + return mSelectedIndex; + } + + ui + void fill(out gb_ViewModel viewModel) + { + let item = players[consolePlayer].mo.inv; + int index = 0; + while (item != NULL) + { + if (item.bInvBar) + { + string tag = item.getTag(); + let icon = BaseStatusBar.getInventoryIcon(item, BaseStatusBar.DI_AltIconFirst); + //int(BaseStatusBar.getInventoryIcon(item, BaseStatusBar.DI_AltIconFirst)); + viewModel.tags .push(tag); + viewModel.slots .push(index + 1); + viewModel.indices .push(index); + viewModel.icons .push(icon); + viewModel.iconScaleXs .push(1); + viewModel.iconScaleYs .push(1); + viewModel.quantity1 .push(item.maxAmount > 1 ? item.amount : -1); + viewModel.maxQuantity1.push(item.maxAmount); + viewModel.quantity2 .push(-1); + viewModel.maxQuantity2.push(-1); + + ++index; + } + item = item.inv; + } + + mSelectedIndex = min(mSelectedIndex, getItemsNumber() - 1); + if (mSelectedIndex == -1 && getItemsNumber() > 0) mSelectedIndex = 0; + viewModel.selectedIndex = mSelectedIndex; + } + +// private: //////////////////////////////////////////////////////////////////////////////////////// + + private static + int getItemsNumber() + { + let item = players[consolePlayer].mo.inv; + int result = 0; + while (item != NULL) + { + result += item.bInvBar; + item = item.inv; + } + return result; + } + + private int mSelectedIndex; + +} // class gb_InventoryMenu diff --git a/zscript/gearbox/inventory_user.zs b/zscript/gearbox/inventory_user.zs new file mode 100644 index 0000000000..d7a9844807 --- /dev/null +++ b/zscript/gearbox/inventory_user.zs @@ -0,0 +1,80 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2021 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +/** + * We could use inventory items directly, but player cannot use items when + * totally frozen. Therefore, we have to wait until player is not frozen. + */ +class gb_InventoryUser play +{ + + static + gb_InventoryUser from() + { + return new("gb_InventoryUser"); + } + + void use() + { + for (uint i = 0; i < mItemQueue.size();) + { + PlayerPawn player = mPlayerQueue[i].mo; + if (player == NULL) + { + deleteFromQueue(i); + continue; + } + + Inventory item = player.findInventory(mItemQueue[i]); + if (item == NULL) + { + deleteFromQueue(i); + continue; + } + + if (player.player.isTotallyFrozen()) + { + ++i; + continue; + } + else + { + player.useInventory(item); + deleteFromQueue(i); + } + } + } + + void addToQueue(PlayerInfo player, string item) + { + mPlayerQueue.push(player); + mItemQueue.push(item); + } + +// private: //////////////////////////////////////////////////////////////////////////////////////// + + private + void deleteFromQueue(uint index) + { + mPlayerQueue.delete(index); + mItemQueue.delete(index); + } + + private Array mPlayerQueue; + private Array mItemQueue; + +} // class gb_InventoryUser diff --git a/zscript/gearbox/netevent_processor.zs b/zscript/gearbox/netevent_processor.zs new file mode 100644 index 0000000000..157202bfc3 --- /dev/null +++ b/zscript/gearbox/netevent_processor.zs @@ -0,0 +1,59 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2020-2021 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +class gb_NeteventProcessor play +{ + + static + gb_NeteventProcessor from(gb_Changer changer) + { + let result = new("gb_NeteventProcessor"); + result.mChanger = changer; + return result; + } + + int process(ConsoleEvent event) + { + if (event.name.left(3) != "gb_") return InputNothing; + + Array args; + event.name.split(args, ":"); + + PlayerInfo player = players[event.player]; + + if (args[0] == "gb_select_weapon") mChanger.selectWeapon(player, args[1]); + else if (args[0] == "gb_use_item" ) mChanger.useItem (player, args[1]); + else if (args[0] == "gb_set_angles" ) mChanger.setAngles (player, args[1].toDouble() + , args[2].toDouble() + ); + else if (args[0] == "gb_freeze_player") mChanger.freezePlayer(player, args[1].toInt() + , args[2].toInt() + , args[3].toDouble() + , args[4].toDouble() + , args[5].toDouble() + ); + else if (args[0] == "gb_reset_custom_order") return InputResetCustomOrder; + else if (args[0] == "gb_give_item") mChanger.giveItem(player,args[1]); + + return InputNothing; + } + +// private: //////////////////////////////////////////////////////////////////////////////////////// + + private gb_Changer mChanger; + +} // class gb_NeteventProcessor diff --git a/zscript/gearbox/options.zs b/zscript/gearbox/options.zs new file mode 100644 index 0000000000..5f97246f6a --- /dev/null +++ b/zscript/gearbox/options.zs @@ -0,0 +1,173 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2020-2021 + * Carrascado 2022 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +class gb_Options +{ + + static + gb_Options from() + { + let result = new("gb_Options"); + + result.mScale = gb_Cvar.from("gb_scale"); + result.mColor = gb_Cvar.from("gb_color"); + result.mDimColor = gb_Cvar.from("gb_dim_color"); + result.mViewType = gb_Cvar.from("gb_view_type"); + result.mIsDimEnabled = gb_Cvar.from("gb_enable_dim"); + result.mIsBlurEnabled = gb_Cvar.from("gb_enable_blur"); + result.mWheelTint = gb_Cvar.from("gb_wheel_tint"); + result.mMultiWheelLimit = gb_Cvar.from("gb_multiwheel_limit"); + result.mShowTags = gb_Cvar.from("gb_show_tags"); + result.mShowWeaponTagsOnChange = gb_Cvar.from("DisplayNameTags"); + result.mIsPositionLocked = gb_Cvar.from("gb_lock_positions"); + result.mFrozenCanOpen = gb_Cvar.from("gb_frozen_can_open"); + result.mPreserveAspectRatio = gb_Cvar.from("hud_AspectScale"); + + result.mOpenOnScroll = gb_Cvar.from("gb_open_on_scroll"); + result.mOpenOnSlot = gb_Cvar.from("gb_open_on_slot"); + result.mReverseSlotCycleOrder = gb_Cvar.from("gb_reverse_slot_cycle_order"); + result.mSelectFirstSlotWeapon = gb_Cvar.from("gb_select_first_slot_weapon"); + result.mMouseInWheel = gb_Cvar.from("gb_mouse_in_wheel"); + result.mSelectOnKeyUp = gb_Cvar.from("gb_select_on_key_up"); + result.mNoMenuIfOne = gb_Cvar.from("gb_no_menu_if_one"); + result.mTimeFreeze = gb_Cvar.from("gb_time_freeze"); + result.mOnAutomap = gb_Cvar.from("gb_on_automap"); + result.mEnableSounds = gb_Cvar.from("gb_enable_sounds"); + + result.mMouseSensitivityX = gb_Cvar.from("gb_mouse_sensitivity_x"); + result.mMouseSensitivityY = gb_Cvar.from("gb_mouse_sensitivity_y"); + result.mInvertMouseX = gb_Cvar.from("invertMouseX"); + result.mInvertMouseY = gb_Cvar.from("invertMouse"); + + result.mBlocksPositionX = gb_Cvar.from("gb_blocks_position_x"); + result.mBlocksPositionY = gb_Cvar.from("gb_blocks_position_y"); + + result.mTextScale = gb_Cvar.from("gb_text_scale"); + result.mTextPositionX = gb_Cvar.from("gb_text_position_x"); + result.mTextPositionY = gb_Cvar.from("gb_text_position_y"); + result.mTextPositionYMax = gb_Cvar.from("gb_text_position_y_max"); + result.mTextUsualColor = gb_Cvar.from("gb_text_usual_color"); + result.mTextSelectedColor = gb_Cvar.from("gb_text_selected_color"); + + result.mWheelPosition = gb_Cvar.from("gb_wheel_position"); + result.mWheelScale = gb_Cvar.from("gb_wheel_scale"); + + return result; + } + + int getViewType() const { return mViewType .getInt(); } + int getScale() const { return mScale .getInt(); } + int getColor() const { return mColor .getInt(); } + int getDimColor() const { return mDimColor .getInt(); } + bool isDimEnabled() const { return mIsDimEnabled .getBool(); } + bool isBlurEnabled() const { return mIsBlurEnabled .getBool(); } + bool getWheelTint() const { return mWheelTint .getBool(); } + int getMultiWheelLimit() const { return mMultiWheelLimit .getInt(); } + bool isShowingTags() const { return mShowTags .getBool(); } + bool isShowingWeaponTagsOnChange() const { return mShowWeaponTagsOnChange.getInt() & 2; } + bool isPositionLocked() const { return mIsPositionLocked .getBool(); } + bool isFrozenCanOpen() const { return mFrozenCanOpen .getBool(); } + bool isPreservingAspectRatio() const { return mPreserveAspectRatio .getBool(); } + + bool isOpenOnScroll() const { return mOpenOnScroll .getBool(); } + bool isOpenOnSlot() const { return mOpenOnSlot .getBool(); } + bool isSlotCycleOrderReversed() const { return mReverseSlotCycleOrder .getBool(); } + bool isSelectFirstSlotWeapon() const { return mSelectFirstSlotWeapon .getBool(); } + bool isMouseInWheel() const { return mMouseInWheel .getBool(); } + bool isSelectOnKeyUp() const { return mSelectOnKeyUp .getBool(); } + bool isNoMenuIfOne() const { return mNoMenuIfOne .getBool(); } + bool isOnAutomap() const { return mOnAutomap .getBool(); } + bool isSoundEnabled() const { return mEnableSounds .getBool(); } + + int getTimeFreezeMode() const { return mTimeFreeze .getInt(); } + + vector2 getMouseSensitivity() const + { + double xDirection = mInvertMouseX.getBool() ? -1 : 1; + double yDirection = mInvertMouseY.getBool() ? -1 : 1; + return ( mMouseSensitivityX.getDouble() * xDirection + , mMouseSensitivityY.getDouble() * yDirection + ); + } + + vector2 getBlocksPosition() const + { + return (mBlocksPositionX.getDouble(), mBlocksPositionY.getDouble()); + } + + int getTextScale() const { return mTextScale .getInt(); } + int getTextUsualColor() const { return mTextUsualColor .getInt(); } + int getTextSelectedColor() const { return mTextSelectedColor.getInt(); } + double getTextPositionYMax() const { return mTextPositionYMax.getDouble(); } + + vector2 getTextPosition() const + { + return (mTextPositionX.getDouble(), mTextPositionY.getDouble()); + } + + + double getWheelPosition() const { return mWheelPosition.getDouble(); } + double getWheelScale() const { return mWheelScale.getDouble(); } + +// private: //////////////////////////////////////////////////////////////////////////////////////// + + private gb_Cvar mScale; + private gb_Cvar mColor; + private gb_Cvar mDimColor; + private gb_Cvar mViewType; + private gb_Cvar mIsDimEnabled; + private gb_Cvar mIsBlurEnabled; + private gb_Cvar mWheelTint; + private gb_Cvar mMultiWheelLimit; + private gb_Cvar mShowTags; + private gb_Cvar mShowWeaponTagsOnChange; + private gb_Cvar mIsPositionLocked; + + private gb_Cvar mOpenOnScroll; + private gb_Cvar mOpenOnSlot; + private gb_Cvar mReverseSlotCycleOrder; + private gb_Cvar mSelectFirstSlotWeapon; + private gb_Cvar mMouseInWheel; + private gb_Cvar mSelectOnKeyUp; + private gb_Cvar mNoMenuIfOne; + private gb_Cvar mTimeFreeze; + private gb_Cvar mOnAutomap; + private gb_Cvar mEnableSounds; + private gb_Cvar mFrozenCanOpen; + private gb_Cvar mPreserveAspectRatio; + + private gb_Cvar mMouseSensitivityX; + private gb_Cvar mMouseSensitivityY; + + private gb_Cvar mInvertMouseX; + private gb_Cvar mInvertMouseY; + + private gb_Cvar mBlocksPositionX; + private gb_Cvar mBlocksPositionY; + + private gb_Cvar mTextScale; + private gb_Cvar mTextPositionX; + private gb_Cvar mTextPositionY; + private gb_Cvar mTextPositionYMax; + private gb_Cvar mTextUsualColor; + private gb_Cvar mTextSelectedColor; + + private gb_Cvar mWheelPosition; + private gb_Cvar mWheelScale; + +} // class gb_Options diff --git a/zscript/gearbox/pb/tokens.zs b/zscript/gearbox/pb/tokens.zs new file mode 100644 index 0000000000..cd01dea9ee --- /dev/null +++ b/zscript/gearbox/pb/tokens.zs @@ -0,0 +1,784 @@ +// +// PB_SpecialWheel_Mode contains info used by the event handler to know what to display in screen +// and what to give to the player based on what they choose; +// +// + +Class PB_SpecialWheel_Mode +{ + string img; // icon, as string, can be a full path + string Alias; // name of the mode + string tokentogive; // token class to give + double scalex; // x scale of the icon + double scaley; // y scale of the icon +} + +// +// these hold the info about the weapon wheel icons for their respective weapon +// + +class WheelInfoContainer +{ + virtual void GetSpecials(in out array spw, actor requester) //receives an array and fills it with PB_SpecialWheel_Mode instances, also receives a pointer to the actual player, so it can check for tokens + { + if(!spw || !requester) + return; + } + + virtual int GetSPCount(actor requester) //this was the simplest and fastest thing i could think, returns the ammount of specials this class has + { + return 0; + } +} + +Class PB_CarbineWeaponWheel : wheelinfocontainer +{ + override int GetSPCount(actor requester) + { + return 4; + } + + override void GetSpecials(in out array spw, actor requester) + { + if(!spw || !requester) + return; + + bool dualling = requester.FindInventory("DualWieldingCarbines"); + + vector2 iconScale = (0.65, 0.65); + + PB_SpecialWheel_Mode carbine_fullauto = new ("PB_SpecialWheel_Mode"); + carbine_fullauto.img = "graphics/pywheel/Carbine_Auto.png"; + carbine_fullauto.Alias = "Toggle Full-Auto Fire"; + carbine_fullauto.tokentogive = "SelectCarbine_FullAutoFire"; + carbine_fullauto.scalex = iconscale.x; + carbine_fullauto.scaley = iconscale.y; + + + + PB_SpecialWheel_Mode carbine_burst = new ("PB_SpecialWheel_Mode"); + carbine_burst.img = "graphics/pywheel/Carbine_Semi.png"; + carbine_burst.Alias = "Toggle Semi-Auto Fire"; + carbine_burst.tokentogive = "SelectCarbine_SemiFire"; + carbine_burst.scalex = iconscale.x; + carbine_burst.scaley = iconscale.y; + + + PB_SpecialWheel_Mode carbine_semi = new ("PB_SpecialWheel_Mode"); + carbine_semi.img = "graphics/pywheel/Carbine_Burst.png"; + carbine_semi.Alias = "Toggle Burst Fire"; + carbine_semi.tokentogive = "SelectCarbine_BurstFire"; + carbine_semi.scalex = iconscale.x; + carbine_semi.scaley = iconscale.y; + + + spw.Push(carbine_fullauto); + spw.Push(carbine_burst); + spw.Push(carbine_semi); + + + if(!dualling) + { + PB_SpecialWheel_Mode carbine_dualwield = new ("PB_SpecialWheel_Mode"); + carbine_dualwield.img = "graphics/pywheel/Carbine_Dual.png"; + carbine_dualwield.Alias = "Akimbo Carbines"; + carbine_dualwield.tokentogive = "SelectCarbine_DualWield"; + carbine_dualwield.scalex = iconscale.x; + carbine_dualwield.scaley = iconscale.y; + + spw.Push(carbine_dualwield); + + } + else + { + PB_SpecialWheel_Mode carbine_dualwield = new ("PB_SpecialWheel_Mode"); + carbine_dualwield.img = "sprites/weapons/Slot 4/Carbine/CB00Z0.png"; + carbine_dualwield.Alias = "Single Carbine"; + carbine_dualwield.tokentogive = "SelectCarbine_DualWield"; + carbine_dualwield.scalex = iconscale.x; + carbine_dualwield.scaley = iconscale.y; + + spw.Push(carbine_dualwield); + } + + } +} + +class PB_pistolWheel : wheelinfocontainer +{ + override int GetSPCount(actor requester) + { + return 3; + } + + override void GetSpecials(in out array spw, actor requester) + { + if(!spw || !requester) + return; + + vector2 iconScale = (0.75, 0.75); + + //check suppresor + if(requester.FindInventory("SilencerEquipped")) + { + PB_SpecialWheel_Mode pistol_unsilenced = new ("PB_SpecialWheel_Mode"); + pistol_unsilenced.img = "graphics/pywheel/PISTOL_5.png"; + pistol_unsilenced.Alias = "Detach Suppressor"; + pistol_unsilenced.tokentogive = "SelectPistolSuppressor"; + pistol_unsilenced.scalex = iconscale.x; + pistol_unsilenced.scaley = iconscale.y; + + spw.Push(pistol_unsilenced); + } + else + { + PB_SpecialWheel_Mode pistol_silencer = new ("PB_SpecialWheel_Mode"); + pistol_silencer.img = "graphics/pywheel/PISTOL_1.png"; + pistol_silencer.Alias = "Attach Suppressor"; + pistol_silencer.tokentogive = "SelectPistolSuppressor"; + pistol_silencer.scalex = iconscale.x; + pistol_silencer.scaley = iconscale.y; + + spw.Push(pistol_silencer); + } + + //check dw + if(requester.FindInventory("DualWieldingPistols")) + { + PB_SpecialWheel_Mode pistol_single = new ("PB_SpecialWheel_Mode"); + pistol_single.img = "graphics/pywheel/PISTOL_0.png"; + pistol_single.Alias = "Single Pistol"; + pistol_single.tokentogive = "SelectDualWieldPistols"; + pistol_single.scalex = iconscale.x; + pistol_single.scaley = iconscale.y; + + spw.Push(pistol_single); + } + else + { + PB_SpecialWheel_Mode pistol_dual = new ("PB_SpecialWheel_Mode"); + pistol_dual.img = "graphics/pywheel/PISTOL_4.png"; + pistol_dual.Alias = "Akimbo Pistols"; + pistol_dual.tokentogive = "SelectDualWieldPistols"; + pistol_dual.scalex = iconscale.x; + pistol_dual.scaley = iconscale.y; + + spw.Push(pistol_dual); + } + + //check burst + if(requester.FindInventory("ToggledPistolBurstFire")) + { + PB_SpecialWheel_Mode pistol_semi = new ("PB_SpecialWheel_Mode"); + pistol_semi.img = "graphics/pywheel/PISTOL_3.png"; + pistol_semi.Alias = "Semi Fire"; + pistol_semi.tokentogive = "SelectPistolBurstFire"; + pistol_semi.scalex = iconscale.x; + pistol_semi.scaley = iconscale.y; + + spw.Push(pistol_semi); + } + else + { + PB_SpecialWheel_Mode pistol_burst = new ("PB_SpecialWheel_Mode"); + pistol_burst.img = "graphics/pywheel/PISTOL_2.png"; + pistol_burst.Alias = "Burst Fire"; + pistol_burst.tokentogive = "SelectPistolBurstFire"; + pistol_burst.scalex = iconscale.x; + pistol_burst.scaley = iconscale.y; + + spw.Push(pistol_burst); + } + + } +} + +Class PB_SGLWheel : wheelinfocontainer +{ + override int GetSPCount(actor requester) + { + return 5; + } + + override void GetSpecials(in out array spw, actor requester) + { + if(!spw || !requester) + return; + + vector2 iconScale = (0.5, 0.5); + + PB_SpecialWheel_Mode grenade_impact = new ("PB_SpecialWheel_Mode"); + grenade_impact.img = "graphics/pywheel/grenade_impact.png"; + grenade_impact.Alias = "Frag Grenade"; + grenade_impact.tokentogive = "GrenadeTypeImpact"; + grenade_impact.scalex = iconscale.x; + grenade_impact.scaley = iconscale.y; + + PB_SpecialWheel_Mode grenade_sticky = new ("PB_SpecialWheel_Mode"); + grenade_sticky.img = "graphics/pywheel/grenade_sticky.png"; + grenade_sticky.Alias = "Sticky Bomb"; + grenade_sticky.tokentogive = "GrenadeTypeSticky"; + grenade_sticky.scalex = iconscale.x; + grenade_sticky.scaley = iconscale.y; + + PB_SpecialWheel_Mode grenade_incendiary = new ("PB_SpecialWheel_Mode"); + grenade_incendiary.img = "graphics/pywheel/grenade_incendiary.png"; + grenade_incendiary.Alias = "Incendiary Grenade"; + grenade_incendiary.tokentogive = "GrenadeTypeIncendiary"; + grenade_incendiary.scalex = iconscale.x; + grenade_incendiary.scaley = iconscale.y; + + PB_SpecialWheel_Mode grenade_cryo = new ("PB_SpecialWheel_Mode"); + grenade_cryo.img = "graphics/pywheel/grenade_cryo.png"; + grenade_cryo.Alias = "Cryogenic Grenade"; + grenade_cryo.tokentogive = "GrenadeTypeCryo"; + grenade_cryo.scalex = iconscale.x; + grenade_cryo.scaley = iconscale.y; + + PB_SpecialWheel_Mode grenade_acid = new ("PB_SpecialWheel_Mode"); + grenade_acid.img = "graphics/pywheel/grenade_acid.png"; + grenade_acid.Alias = "Acid Grenade"; + grenade_acid.tokentogive = "GrenadeTypeAcid"; + grenade_acid.scalex = iconscale.x; + grenade_acid.scaley = iconscale.y; + + spw.Push(grenade_impact); + spw.Push(grenade_sticky); + spw.Push(grenade_incendiary); + spw.Push(grenade_cryo); + spw.Push(grenade_acid); + } +} + + +Class PB_SMGWheel : wheelinfocontainer +{ + override int GetSPCount(actor requester) + { + return 2; + } + + override void GetSpecials(in out array spw, actor requester) + { + if(!spw || !requester) + return; + + vector2 iconScale = (0.5, 0.5); + + if(!requester.FindInventory("DualWieldingSMGs")) + { + PB_SpecialWheel_Mode smg_dualwield = new ("PB_SpecialWheel_Mode"); + smg_dualwield.img = "graphics/pywheel/SMG/SMG_DUAL.png"; + smg_dualwield.Alias = "Akimbo SMGs"; + smg_dualwield.tokentogive = "SelectDualWieldSMG"; + smg_dualwield.scalex = iconscale.x; + smg_dualwield.scaley = iconscale.y; + + spw.Push(smg_dualwield); + } + else + { + PB_SpecialWheel_Mode smg_dualwield = new ("PB_SpecialWheel_Mode"); + smg_dualwield.img = "sprites/weapons/Slot 2/UACSMG/Pickup/ATFLA0.png"; + smg_dualwield.Alias = "Single SMG"; + smg_dualwield.tokentogive = "SelectDualWieldSMG"; + smg_dualwield.scalex = iconscale.x; + smg_dualwield.scaley = iconscale.y; + + spw.Push(smg_dualwield); + } + if(!requester.FindInventory("LaserSightActivated")) + { + PB_SpecialWheel_Mode smg_laser = new ("PB_SpecialWheel_Mode"); + smg_laser.img = "graphics/pywheel/SMG/SMG_LASER.png"; + smg_laser.Alias = "Activate Laser Sight"; + smg_laser.tokentogive = "SelectLaserSight"; + smg_laser.scalex = iconscale.x; + smg_laser.scaley = iconscale.y; + + spw.Push(smg_laser); + } + else + { + PB_SpecialWheel_Mode smg_laser = new ("PB_SpecialWheel_Mode"); + smg_laser.img = "sprites/weapons/Slot 2/UACSMG/Pickup/ATFLA0.png"; + smg_laser.Alias = "Deactivate Laser Sight"; + smg_laser.tokentogive = "SelectLaserSight"; + smg_laser.scalex = iconscale.x; + smg_laser.scaley = iconscale.y; + + spw.Push(smg_laser); + } + } +} + +Class PB_RifleWheel : wheelinfocontainer +{ + override int GetSPCount(actor requester) + { + return 3; + } + + override void GetSpecials(in out array spw, actor requester) + { + if(!spw || !requester) + return; + + vector2 iconScale = (0.55, 0.55); + + // Check Dual Wield Icons + if(requester.FindInventory("DualWieldingDMRs")) + { + PB_SpecialWheel_Mode rifle_single = new ("PB_SpecialWheel_Mode"); + rifle_single.img = "graphics/pywheel/hdmr_single.png"; + rifle_single.Alias = "Single DMR"; + rifle_single.tokentogive = "SelectDualWieldRifles"; + rifle_single.scalex = iconscale.x; + rifle_single.scaley = iconscale.y; + + spw.Push(rifle_single); + } + else + { + PB_SpecialWheel_Mode rifle_dual = new ("PB_SpecialWheel_Mode"); + rifle_dual.img = "graphics/pywheel/hdmr_dual.png"; + rifle_dual.Alias = "Akimbo DMRs"; + rifle_dual.tokentogive = "SelectDualWieldRifles"; + rifle_dual.scalex = iconscale.x; + rifle_dual.scaley = iconscale.y; + + spw.Push(rifle_dual); + } + + if(requester.FindInventory("HDMRGrenadeMode")) + { + PB_SpecialWheel_Mode rifle_grenade_off = new ("PB_SpecialWheel_Mode"); + rifle_grenade_off.img = "graphics/pywheel/hdmr_grenade_off.png"; + rifle_grenade_off.Alias = "Aiming Secondary Fire"; + rifle_grenade_off.tokentogive = "SelectHDMRGrenade"; + rifle_grenade_off.scalex = iconscale.x; + rifle_grenade_off.scaley = iconscale.y; + + spw.Push(rifle_grenade_off); + } + else + { + PB_SpecialWheel_Mode rifle_grenade_on = new ("PB_SpecialWheel_Mode"); + rifle_grenade_on.img = "graphics/pywheel/hdmr_grenade_on.png"; + rifle_grenade_on.Alias = "Grenade Secondary Fire"; + rifle_grenade_on.tokentogive = "SelectHDMRGrenade"; + rifle_grenade_on.scalex = iconscale.x; + rifle_grenade_on.scaley = iconscale.y; + + spw.Push(rifle_grenade_on); + } + + + if(requester.FindInventory("HDMRSniperMode")) + { + PB_SpecialWheel_Mode rifle_normal = new ("PB_SpecialWheel_Mode"); + rifle_normal.img = "graphics/pywheel/hdmr_normal.png"; + rifle_normal.Alias = "DMR Mode"; + rifle_normal.tokentogive = "SelectHDMRMode"; + rifle_normal.scalex = iconscale.x; + rifle_normal.scaley = iconscale.y; + + spw.Push(rifle_normal); + } + else + { + PB_SpecialWheel_Mode rifle_sniper = new ("PB_SpecialWheel_Mode"); + rifle_sniper.img = "graphics/pywheel/hdmr_sniper.png"; + rifle_sniper.Alias = "Heavy Sniper Mode"; + rifle_sniper.tokentogive = "SelectHDMRMode"; + rifle_sniper.scalex = iconscale.x; + rifle_sniper.scaley = iconscale.y; + + spw.Push(rifle_sniper); + } + } +} + +Class PB_QSGWheel : wheelinfocontainer +{ + override int GetSPCount(actor requester) + { + return 3; + } + + override void GetSpecials(in out array spw, actor requester) + { + if(!spw || !requester) + return; + + vector2 iconScale = (0.55, 0.55); + + if(requester.FindInventory("QuadAkimboMode")) + { + PB_SpecialWheel_Mode qsg_undual = new ("PB_SpecialWheel_Mode"); + qsg_undual.img = "graphics/pywheel/Quad_Single.png"; + qsg_undual.Alias = "Single Quad Shotgun"; + qsg_undual.tokentogive = "SelectDualWieldQuads"; + qsg_undual.scalex = iconscale.x; + qsg_undual.scaley = iconscale.y; + + spw.Push(qsg_undual); + } + else + { + PB_SpecialWheel_Mode qsg_dual = new ("PB_SpecialWheel_Mode"); + qsg_dual.img = "graphics/pywheel/Quad_Dual.png"; + qsg_dual.Alias = "Akimbo Quad Shotguns"; + qsg_dual.tokentogive = "SelectDualWieldQuads"; + qsg_dual.scalex = iconscale.x; + qsg_dual.scaley = iconscale.y; + + spw.Push(qsg_dual); + } + + if(requester.FindInventory("FullBlastMode")) + { + PB_SpecialWheel_Mode qsg_halfnormal = new ("PB_SpecialWheel_Mode"); + qsg_halfnormal.img = "graphics/pywheel/Quad_Half.png"; + qsg_halfnormal.Alias = "Half Blast"; + qsg_halfnormal.tokentogive = "BlastToggle"; + qsg_halfnormal.scalex = iconscale.x; + qsg_halfnormal.scaley = iconscale.y; + + spw.Push(qsg_halfnormal); + } + else + { + PB_SpecialWheel_Mode qsg_fullnormal = new ("PB_SpecialWheel_Mode"); + qsg_fullnormal.img = "graphics/pywheel/Quad_Full.png"; + qsg_fullnormal.Alias = "Full Blast"; + qsg_fullnormal.tokentogive = "BlastToggle"; + qsg_fullnormal.scalex = iconscale.x; + qsg_fullnormal.scaley = iconscale.y; + + spw.Push(qsg_fullnormal); + } + + if(requester.FindInventory("BreathMode")) + { + PB_SpecialWheel_Mode qsg_shell = new ("PB_SpecialWheel_Mode"); + qsg_shell.img = "graphics/pywheel/Quad_Shells.png"; + qsg_shell.Alias = "Shells mode"; + qsg_shell.tokentogive = "BreathToggle"; + qsg_shell.scalex = iconscale.x; + qsg_shell.scaley = iconscale.y; + + spw.Push(qsg_shell); + } + else + { + PB_SpecialWheel_Mode qsg_demon = new ("PB_SpecialWheel_Mode"); + qsg_demon.img = "graphics/pywheel/Quad_Demonic.png"; + qsg_demon.Alias = "Demonic Breath mode"; + qsg_demon.tokentogive = "BreathToggle"; + qsg_demon.scalex = iconscale.x; + qsg_demon.scaley = iconscale.y; + + spw.Push(qsg_demon); + } + } +} + +Class PB_CryoRifleWheel : wheelinfocontainer +{ + override int GetSPCount(actor requester) + { + return 4; + } + + override void GetSpecials(in out array spw, actor requester) + { + if(!spw || !requester) + return; + + vector2 iconScale = (0.8, 0.8); + + PB_SpecialWheel_Mode cryorifle_missile = new ("PB_SpecialWheel_Mode"); + cryorifle_missile.img = "graphics/pywheel/CryoRifle_Missile.png"; + cryorifle_missile.Alias = "Primary: Ice Missile"; + cryorifle_missile.tokentogive = "FireModeCryoRifleMissile_WW"; + cryorifle_missile.scalex = iconscale.x; + cryorifle_missile.scaley = iconscale.y; + + spw.Push(cryorifle_missile); + + PB_SpecialWheel_Mode cryorifle_beam = new ("PB_SpecialWheel_Mode"); + cryorifle_beam.img = "graphics/pywheel/cryorifle_beam.png"; + cryorifle_beam.Alias = "Primary: Ice Beam"; + cryorifle_beam.tokentogive = "FireModeCryoRifleBeam_WW"; + cryorifle_beam.scalex = iconscale.x; + cryorifle_beam.scaley = iconscale.y; + + spw.Push(cryorifle_beam); + + PB_SpecialWheel_Mode cryorifle_spear = new ("PB_SpecialWheel_Mode"); + cryorifle_spear.img = "graphics/pywheel/CryoRifle_Spear.png"; + cryorifle_spear.Alias = "Secondary: Ice Spear"; + cryorifle_spear.tokentogive = "FireModeCryoRifleSpear_WW"; + cryorifle_spear.scalex = iconscale.x; + cryorifle_spear.scaley = iconscale.y; + + spw.Push(cryorifle_spear); + + PB_SpecialWheel_Mode cryorifle_flak = new ("PB_SpecialWheel_Mode"); + cryorifle_flak.img = "graphics/pywheel/CryoRifle_Flak.png"; + cryorifle_flak.Alias = "Secondary: Ice Flak"; + cryorifle_flak.tokentogive = "FireModeCryoRifleFlak_WW"; + cryorifle_flak.scalex = iconscale.x; + cryorifle_flak.scaley = iconscale.y; + + spw.Push(cryorifle_flak); + } +} + +Class PB_MinigunWheel : wheelinfocontainer +{ + override int GetSPCount(actor requester) + { + if(requester.FindInventory("MinigunUpgraded")) + return 3; + return 2; + } + + override void GetSpecials(in out array spw, actor requester) + { + if(!spw || !requester) + return; + + vector2 iconScale = (0.8, 0.8); + + PB_SpecialWheel_Mode minigun_chaingun = new ("PB_SpecialWheel_Mode"); + minigun_chaingun.img = "graphics/pywheel/Minigun_1.png"; + minigun_chaingun.Alias = "Chaingun Mode"; + minigun_chaingun.tokentogive = "SelectMinigun_Chaingun"; + minigun_chaingun.scalex = iconscale.x; + minigun_chaingun.scaley = iconscale.y; + + spw.Push(minigun_chaingun); + + PB_SpecialWheel_Mode minigun_gatling = new ("PB_SpecialWheel_Mode"); + minigun_gatling.img = "graphics/pywheel/Minigun_2.png"; + minigun_gatling.Alias = "Gatling Mode"; + minigun_gatling.tokentogive = "SelectMinigun_Gatling"; + minigun_gatling.scalex = iconscale.x; + minigun_gatling.scaley = iconscale.y; + + spw.Push(minigun_gatling); + + + if(requester.FindInventory("MinigunUpgraded")) + { + PB_SpecialWheel_Mode minigun_triple = new ("PB_SpecialWheel_Mode"); + minigun_triple.img = "graphics/pywheel/Minigun_3.png"; + minigun_triple.Alias = "Triple Rotary Mode"; + minigun_triple.tokentogive = "SelectMinigun_Triple"; + minigun_triple.scalex = iconscale.x; + minigun_triple.scaley = iconscale.y; + + spw.Push(minigun_triple); + } + } +} + +Class PB_PumpShotgunWheel : wheelinfocontainer +{ + override int GetSPCount(actor requester) + { + return 3; + } + + override void GetSpecials(in out array spw, actor requester) + { + if(!spw || !requester) + return; + + vector2 iconScale = (0.7, 0.7); + + if(requester.FindInventory("DragonBreathUpgrade")) + { + PB_SpecialWheel_Mode shotgun_dragonbreath = new ("PB_SpecialWheel_Mode"); + shotgun_dragonbreath.img = "graphics/pywheel/SG_DB.png"; + shotgun_dragonbreath.Alias = "Dragon's Breath Shells"; + shotgun_dragonbreath.tokentogive = "SelectShotgun_Dragonsbreath"; + shotgun_dragonbreath.scalex = iconscale.x; + shotgun_dragonbreath.scaley = iconscale.y; + + spw.Push(shotgun_dragonbreath); + } + else + { + PB_SpecialWheel_Mode shotgun_No = new ("PB_SpecialWheel_Mode"); + shotgun_No.img = "graphics/pywheel/SG_NO.png"; + shotgun_No.Alias = "Not Available"; + shotgun_No.tokentogive = "SelectShotgun_No"; + shotgun_No.scalex = iconscale.x; + shotgun_No.scaley = iconscale.y; + + spw.Push(shotgun_No); + } + + PB_SpecialWheel_Mode shotgun_buckshot = new ("PB_SpecialWheel_Mode"); + shotgun_buckshot.img = "graphics/pywheel/SG_Buck.png"; + shotgun_buckshot.Alias = "Buckshot Shells"; + shotgun_buckshot.tokentogive = "SelectShotgun_Buckshot"; + shotgun_buckshot.scalex = iconscale.x; + shotgun_buckshot.scaley = iconscale.y; + + + PB_SpecialWheel_Mode shotgun_slugshot = new ("PB_SpecialWheel_Mode"); + shotgun_slugshot.img = "graphics/pywheel/SG_Slug.png"; + shotgun_slugshot.Alias = "Slug Shells"; + shotgun_slugshot.tokentogive = "SelectShotgun_Slugshot"; + shotgun_slugshot.scalex = iconscale.x; + shotgun_slugshot.scaley = iconscale.y; + + + spw.Push(shotgun_buckshot); + spw.Push(shotgun_slugshot); + } +} + +Class PB_RocketLauncherWheel : wheelinfocontainer +{ + override int GetSPCount(actor requester) + { + return 3; + } + + override void GetSpecials(in out array spw, actor requester) + { + if(!spw || !requester) + return; + + vector2 iconScale = (0.5, 0.5); + + PB_SpecialWheel_Mode rocket_standard = new ("PB_SpecialWheel_Mode"); + rocket_standard.img = "graphics/pywheel/rocket_standard.png"; + rocket_standard.Alias = "Standard Rocket Mode"; + rocket_standard.tokentogive = "RocketLauncher_Standard"; + rocket_standard.scalex = iconscale.x; + rocket_standard.scaley = iconscale.y; + + PB_SpecialWheel_Mode rocket_homing = new ("PB_SpecialWheel_Mode"); + rocket_homing.img = "graphics/pywheel/rocket_homing.png"; + rocket_homing.Alias = "Lock-On Rocket Mode"; + rocket_homing.tokentogive = "RocketLauncher_Homing"; + rocket_homing.scalex = iconscale.x; + rocket_homing.scaley = iconscale.y; + + PB_SpecialWheel_Mode rocket_laser = new ("PB_SpecialWheel_Mode"); + rocket_laser.img = "graphics/pywheel/rocket_laser.png"; + rocket_laser.Alias = "Laser Rocket Mode"; + rocket_laser.tokentogive = "RocketLauncher_Laser"; + rocket_laser.scalex = iconscale.x; + rocket_laser.scaley = iconscale.y; + + /*if(requester.FindInventory("RL_ScopeMode")) { + PB_SpecialWheel_Mode rocket_multi = new ("PB_SpecialWheel_Mode"); + rocket_multi.img = "graphics/pywheel/multirocket.png"; + rocket_multi.Alias = "Multi Rocket Mode"; + rocket_multi.tokentogive = "RocketLauncher_Multi"; + rocket_multi.scalex = iconscale.x; + rocket_multi.scaley = iconscale.y; + + spw.Push(rocket_multi); + } + else + { + PB_SpecialWheel_Mode rocket_scope = new ("PB_SpecialWheel_Mode"); + rocket_scope.img = "graphics/pywheel/rocketscope.png"; + rocket_scope.Alias = "Scope Mode"; + rocket_scope.tokentogive = "RocketLauncher_Scope"; + rocket_scope.scalex = iconscale.x; + rocket_scope.scaley = iconscale.y; + + spw.Push(rocket_scope); + }*/ + + spw.Push(rocket_standard); + spw.Push(rocket_homing); + spw.Push(rocket_laser); + } +} + + +///////////////////// +// +// equipments +// +///////////////////// + +class equipmentCard +{ + //this function fills the respective arrays to correctly display the equipments in the wheel + //if any new equipment is added, create a new class inheriting from this class for the handler to catch it + virtual void InfoFiller(out array tags,out array tokens,out arrayimg,out arraysx,out arraysy) + { + return; + } +} + +class ProxMinCard : equipmentCard +{ + override void InfoFiller(out array tags,out array tokens,out arrayimg,out arraysx,out arraysy) + { + tags.push("Proximity Mine"); + tokens.push("WW_ProximityMineSelected"); + img.push("graphics/pywheel/Equip_Mine.png"); + sx.push(1.3); + sy.push(1.3); + } +} + +Class StunGrenCard : equipmentCard +{ + override void InfoFiller(out array tags,out array tokens,out arrayimg,out arraysx,out arraysy) + { + tags.push("Stun Grenade"); + tokens.push("WW_StunGrenadeSelected"); + img.push("graphics/pywheel/Equip_Stun.png"); + sx.push(1.3); + sy.push(1.3); + } +} + +Class LeechCard : equipmentCard +{ + override void InfoFiller(out array tags,out array tokens,out arrayimg,out arraysx,out arraysy) + { + tags.push("Leech"); + tokens.push("WW_LeechSelected"); + img.push("graphics/pywheel/Equip_Leech.png"); + sx.push(1.3); + sy.push(1.3); + } +} + +class FragGrenCard : equipmentCard +{ + override void InfoFiller(out array tags,out array tokens,out arrayimg,out arraysx,out arraysy) + { + tags.push("Frag Grenade"); + tokens.push("WW_FragGrenadeSelected"); + img.push("graphics/pywheel/Equip_Frag.png"); + sx.push(1.3); + sy.push(1.3); + } +} + +class ShouldCanCard : equipmentCard +{ + override void InfoFiller(out array tags,out array tokens,out arrayimg,out arraysx,out arraysy) + { + tags.push("Shoulder Cannon"); + tokens.push("WW_RevGunSelected"); + img.push("graphics/pywheel/Equip_RevGun.png"); + sx.push(1.3); + sy.push(1.3); + } +} \ No newline at end of file diff --git a/zscript/gearbox/printer.zs b/zscript/gearbox/printer.zs new file mode 100644 index 0000000000..15a4c85fb0 --- /dev/null +++ b/zscript/gearbox/printer.zs @@ -0,0 +1,40 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2020-2021 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +class gb_Printer +{ + + static + void printWeaponData(gb_WeaponData data) + { + uint nWeapons = data.weapons.size(); + uint nSlots = data.slots.size(); + + console.printf("numbers: %d, %d", nWeapons, nSlots); + + for (uint i = 0; i < nWeapons; ++i) + { + let default = getDefaultByType(data.weapons[i]); + console.printf( "%s (%s), slot %d" + , default.getTag() + , default.getClassName() + , data.slots[i] + ); + } + } + +} // class gb_Printer diff --git a/zscript/gearbox/sender.zs b/zscript/gearbox/sender.zs new file mode 100644 index 0000000000..412ecbf3b0 --- /dev/null +++ b/zscript/gearbox/sender.zs @@ -0,0 +1,56 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2020-2021 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +class gb_Sender +{ + + static + void sendSelectEvent(string className) + { + EventHandler.sendNetworkEvent(string.format("gb_select_weapon:%s", className)); + } + + static + void sendUseItemEvent(string className) + { + EventHandler.sendNetworkEvent(string.format("gb_use_item:%s", className)); + } + + static void sendGiveItemEvent(string classname) + { + EventHandler.sendNetworkEvent(string.format("gb_give_item:%s", className)); + } + + static + void sendFreezePlayerEvent(int cheats, vector3 velocity, double gravity) + { + EventHandler.sendNetworkEvent(string.format( "gb_freeze_player:%d:%f:%f:%f:%f" + , cheats + , velocity.x + , velocity.y + , velocity.z + , gravity + )); + } + + static + void sendPlayerAngles(double pitch, double yaw) + { + EventHandler.sendNetworkEvent("gb_set_angles:" .. pitch .. ":" .. yaw); + } + +} // class gb_Sender diff --git a/zscript/gearbox/service/hide_service.zs b/zscript/gearbox/service/hide_service.zs new file mode 100644 index 0000000000..988c28cf7b --- /dev/null +++ b/zscript/gearbox/service/hide_service.zs @@ -0,0 +1,41 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2021 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +class gb_HideService : gb_Service +{ + + override + string get(string className) + { + switch (Name(className)) + { + // SWWM GZ: https://forum.zdoom.org/viewtopic.php?f=43&t=67687 + case 'DualExplodiumGun': + { + string explodiumGunClass = "ExplodiumGun"; + return (players[consolePlayer].mo.countInv(explodiumGunClass) > 1) ? SHOW : HIDE; + } + + default: return IGNORE; + } + } + + const HIDE = "1"; + const SHOW = "0"; + const IGNORE = ""; + +} // class gb_HideService diff --git a/zscript/gearbox/service/icon_service.zs b/zscript/gearbox/service/icon_service.zs new file mode 100644 index 0000000000..35f3d3d5cd --- /dev/null +++ b/zscript/gearbox/service/icon_service.zs @@ -0,0 +1,66 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2021 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +class gb_IconService : gb_Service +{ + + override + string uiGet(string className) + { + /*if (isSmoothDoom()) switch (Name(className)) + { + case 'PerkFist' : return "PUNGB0"; + case 'Z86Chainsaw' : return "CSAWA0"; + case 'PerkShotgun' : return "SHOTA0"; + case 'PerkSuperShotgun' : return "SGN2A0"; + case 'Z86Chaingun' : return "MGUNA0"; + case 'PerkRocketLauncher' : return "LAUNA0"; + case 'BloxPlasmaRifle' : return "PLRLA0"; + case 'Z86BFG9000' : return "BFG9A0"; + }*/ + + switch (Name(className)) + { + case 'Fist' : return "SMFIST0"; + case 'Chainsaw' : return "SMCSAW0"; + case 'Pistol' : return "SMPISG0"; + case 'Shotgun' : return "SMSHOT0"; + case 'SuperShotgun' : return "SMSGN20"; + case 'Chaingun' : return "SMMGUN0"; + case 'RocketLauncher' : return "SMLAUN0"; + case 'PlasmaRifle' : return "SMPLAS0"; + case 'BFG9000' : return "SMBFGG0"; + + default: return IGNORE; + } + } + + const IGNORE = ""; + +// private: //////////////////////////////////////////////////////////////////////////////////////// + + private static + bool isSmoothDoom() + { + string s1 = "SmoothFloatingSkull"; + class c1 = s1; + string s2 = "SmoothDoomImp"; + class c2 = s2; + return (c1 && c2); + } + +} // class gb_IconService1 diff --git a/zscript/gearbox/service/service.zs b/zscript/gearbox/service/service.zs new file mode 100644 index 0000000000..0cf21a48f6 --- /dev/null +++ b/zscript/gearbox/service/service.zs @@ -0,0 +1,126 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2021 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +/** + * This is Service interface. + */ +class gb_Service abstract +{ + + virtual play + string get(string request) + { + return ""; + } + + virtual ui + string uiGet(string request) + { + return ""; + } + +} // class gb_Service + +/** + * Use this class to find and iterate over services. + * + * Example usage: + * + * @code + * ServiceIterator i = ServiceIterator.find("MyService"); + * + * Service s; + * while (s = i.next()) + * { + * string request = ... + * string answer = s.get(request); + * ... + * } + * @endcode + * + * If no services are found, the all calls to next() will return NULL. + */ +class gb_ServiceIterator +{ + /** + * Creates a Service iterator for a service name. It will iterate over all existing Services + * with names that match @a serviceName or have it as a part of their names. + * + * Matching is case-independent. + * + * @param serviceName class name of service to find. + */ + static + gb_ServiceIterator find(string serviceName) + { + let result = new("gb_ServiceIterator"); + + result.mServiceName = serviceName; + result.mClassIndex = 0; + result.findNextService(); + + return result; + } + + /** + * Gets the service and advances the iterator. + * + * @returns service instance, or NULL if no more servers found. + * + * @note Each ServiceIterator will return new instances of services. + */ + gb_Service next() + { + uint classesNumber = AllClasses.size(); + gb_Service result = (mClassIndex == classesNumber) + ? NULL + : gb_Service(new(AllClasses[mClassIndex])); + + ++mClassIndex; + findNextService(); + + return result; + } + +// private: //////////////////////////////////////////////////////////////////////////////////////// + + private + void findNextService() + { + uint classesNumber = AllClasses.size(); + while (mClassIndex < classesNumber && !ServiceNameContains(AllClasses[mClassIndex], mServiceName)) + { + ++mClassIndex; + } + } + + private static + bool serviceNameContains(class aClass, string substring) + { + if (!(aClass is "gb_Service")) return false; + + string className = aClass.getClassName(); + string lowerClassName = className.makeLower(); + string lowerSubstring = substring.makeLower(); + bool result = lowerClassName.indexOf(lowerSubstring) != -1; + return result; + } + + private string mServiceName; + private uint mClassIndex; + +} // class gb_ServiceIterator diff --git a/zscript/gearbox/sounds.zs b/zscript/gearbox/sounds.zs new file mode 100644 index 0000000000..31d0f48c63 --- /dev/null +++ b/zscript/gearbox/sounds.zs @@ -0,0 +1,62 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2021-2022 + * Carrascado 2022 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +class gb_Sounds +{ + + static + gb_Sounds from(gb_Options options) + { + let result = new("gb_Sounds"); + result.mOptions = options; + return result; + } + + void playTick() + { + playSound("gearbox/tick"); + } + + void playOpen() + { + playSound("gearbox/open"); + } + + void playClose() + { + playSound("gearbox/close"); + } + + void playNope() + { + playSound("gearbox/nope"); + } + +// private: //////////////////////////////////////////////////////////////////////////////////////// + + private + void playSound(string sound) + { + if (!mOptions.isSoundEnabled()) return; + + players[consolePlayer].mo.a_StartSound(sound, CHAN_AUTO, CHANF_UI | CHANF_OVERLAP | CHANF_LOCAL); + } + + private gb_Options mOptions; + +} // class gb_Sounds diff --git a/zscript/gearbox/specials_menu.zs b/zscript/gearbox/specials_menu.zs new file mode 100644 index 0000000000..a175d85a8b --- /dev/null +++ b/zscript/gearbox/specials_menu.zs @@ -0,0 +1,140 @@ +class gb_specialsmenu +{ + static gb_specialsmenu from() + { + let nc = new("gb_specialsmenu"); + nc.mSelectedIndex = 0; + return nc; + } + + ui void getspecials(weapon act) + { + ClearSpecials(); //empty the arrays, so everything displays correctly + let pl = players[consoleplayer]; + let toRead = PB_WeaponBase(act); + if(toRead) + { + if(!toRead.wheelinfo) + return; + let wif = wheelinfocontainer(new(toRead.wheelinfo)); + if(!wif || !toRead.hasWheelSpecial) + return; + specialsinfo.clear(); + wif.GetSpecials(specialsinfo,pl.mo); + for(int i = 0; i < specialsinfo.size(); i++) + { + stag.push(specialsinfo[i].Alias); + imgs.push(specialsinfo[i].img); + Xscales.push(specialsinfo[i].scalex); + Yscales.push(specialsinfo[i].scaley); + token.push(specialsinfo[i].tokentogive); + } + + } + } + + static bool thereAreNoSpecials() + { + return getSpecialsNumber() == 0; + } + + private static int getSpecialsNumber() + { + let pl = players[consoleplayer]; + let toRead = PB_WeaponBase(pl.readyweapon); + if(toRead) + { + if(!toRead.wheelinfo) + return 0; + let wif = wheelinfocontainer(new(toRead.wheelinfo)); + if(!wif || !toRead.hasWheelSpecial) + return 0; + return wif.GetSPCount(pl.mo); + + } + return 0; + } + + ui bool selectNext() + { + int nItems = getSpecialsNumber(); + if (nItems == 0) return false; + + mSelectedIndex = (mSelectedIndex + 1) % nItems; + + return true; + } + + ui bool selectPrev() + { + int nItems = getSpecialsNumber(); + if (nItems == 0) return false; + + mSelectedIndex = (mSelectedIndex - 1 + nItems) % nItems; + + return true; + } + + ui bool setSelectedIndex(int index) + { + if (index == -1 || mSelectedIndex == index) return false; + + int nItems = getSpecialsNumber(); + if(nItems == 0) + return false; + index = clamp(index,0,nItems); + + mSelectedIndex = index; + + return true; + } + + ui int getSelectedIndex() const + { + return mSelectedIndex; + } + + string ConfirmSelection() const + { + if(token.size() > 0) + return token[mSelectedIndex]; + return ""; + } + + ui void Fill(out gb_ViewModel viewModel) + { + for(int i = 0; i < stag.size(); i++) + { + viewModel.tags .push(stag[i]); + viewModel.slots .push(i + 1); + viewModel.indices .push(i); + viewModel.icons .push(texman.checkfortexture(imgs[i])); + viewModel.iconScaleXs .push(Xscales[i]); + viewModel.iconScaleYs .push(Yscales[i]); + viewModel.quantity1 .push(-1); //no ammount for you >:( + viewModel.maxQuantity1.push(-1); // + viewModel.quantity2 .push(-1); // + viewModel.maxQuantity2.push(-1); // + } + viewModel.selectedIndex = clamp(mSelectedIndex,0,stag.size()-1); + } + + private ui void ClearSpecials() + { + stag.clear(); + token.clear(); + mSelectedIndex = 0; + imgs.clear(); + Xscales.clear(); + Yscales.clear(); + specialsinfo.clear(); + } + + array stag; + arrayXscales; + arrayYscales; + private int mSelectedIndex; + array token; + array imgs; + Array specialsinfo; +} diff --git a/zscript/gearbox/tools/ammo.zs b/zscript/gearbox/tools/ammo.zs new file mode 100644 index 0000000000..1d7b2ce327 --- /dev/null +++ b/zscript/gearbox/tools/ammo.zs @@ -0,0 +1,27 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2021 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +class gb_Ammo +{ + + static + bool isValid(int ammo, int maxAmmo) + { + return (ammo != -1 && maxAmmo > 0); + } + +} // class gb_Ammo diff --git a/zscript/gearbox/tools/cvar.zs b/zscript/gearbox/tools/cvar.zs new file mode 100644 index 0000000000..f39d2a8b34 --- /dev/null +++ b/zscript/gearbox/tools/cvar.zs @@ -0,0 +1,62 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2020-2021 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +/** + * This class provides access to a user or server Cvar. + * + * Accessing Cvars through this class is faster because calling Cvar.GetCvar() + * is costly. This class caches the result of Cvar.GetCvar() and handles + * loading a savegame. + */ +class gb_Cvar +{ + +// public: ///////////////////////////////////////////////////////////////////////////////////////// + + static + gb_Cvar from(string name) + { + let result = new("gb_Cvar"); + + result.mName = name; + result.load(); + + return result; + } + + string getString() { if (!mCvar) load(); return mCvar.getString(); } + bool getBool() { if (!mCvar) load(); return mCvar.getInt(); } + int getInt() { if (!mCvar) load(); return mCvar.getInt(); } + double getDouble() { if (!mCvar) load(); return mCvar.getFloat(); } + +// private: //////////////////////////////////////////////////////////////////////////////////////// + + private + void load() + { + mCvar = Cvar.getCvar(mName, players[consolePlayer]); + + if (mCvar == NULL) + { + gb_Log.error(string.Format("cvar %s not found", mName)); + } + } + + private string mName; + private transient Cvar mCvar; + +} // class gb_Cvar diff --git a/zscript/gearbox/tools/log.zs b/zscript/gearbox/tools/log.zs new file mode 100644 index 0000000000..a69dce9997 --- /dev/null +++ b/zscript/gearbox/tools/log.zs @@ -0,0 +1,58 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2020-2021 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +class gb_Log +{ + + static + void print(string s) + { + Console.printf("%s", StringTable.localize(s, false)); + } + + static + void notice(string s) + { + Console.printf("[NOTICE] %s: %s", MOD_NAME, StringTable.localize(s, false)); + } + + static + void error(string s) + { + Console.printf("[ERROR] %s: %s.", MOD_NAME, s); + } + + static + void log(string s) + { + Console.printf("[LOG] %s: %s.", MOD_NAME, s); + } + + static + void debug(string s) + { + if (DEBUG_ENABLED) + { + Console.printf("[DEBUG] %s: %s.", MOD_NAME, s); + } + } + + const DEBUG_ENABLED = 0; // == false + + const MOD_NAME = "Gearbox"; + +} // class gb_Log diff --git a/zscript/gearbox/tools/texture_cache.zs b/zscript/gearbox/tools/texture_cache.zs new file mode 100644 index 0000000000..e2afae048a --- /dev/null +++ b/zscript/gearbox/tools/texture_cache.zs @@ -0,0 +1,71 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2021 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +class gb_TextureCache +{ + + static + gb_TextureCache from() + { + return new("gb_TextureCache"); + } + + void load() + { + isLoaded = true; + + // Wheel + circle = TexMan.checkForTexture("gb_circ", TexMan.Type_MiscPatch, TEXTURE_FLAGS); + halfCircle = TexMan.checkForTexture("gb_hcir", TexMan.Type_MiscPatch, TEXTURE_FLAGS); + ammoPip = TexMan.checkForTexture("gb_pip" , TexMan.Type_MiscPatch, TEXTURE_FLAGS); + hand = TexMan.checkForTexture("gb_hand", TexMan.Type_MiscPatch, TEXTURE_FLAGS); + pointer = TexMan.checkForTexture("gb_pntr", TexMan.Type_MiscPatch, TEXTURE_FLAGS); + textBox = TexMan.checkForTexture("gb_desc", TexMan.Type_MiscPatch, TEXTURE_FLAGS); + noIcon = TexMan.checkForTexture("gb_nope", TexMan.Type_MiscPatch, TEXTURE_FLAGS); + + // Blocks gb_wpsel.png + blockBox = TexMan.checkForTexture("gb_box", TexMan.Type_MiscPatch, TEXTURE_FLAGS); + blockBig = TexMan.checkForTexture("gb_weap", TexMan.Type_MiscPatch, TEXTURE_FLAGS); + corner = TexMan.checkForTexture("gb_cor", TexMan.Type_MiscPatch, TEXTURE_FLAGS); + ammoLine = TexMan.checkForTexture("gb_ammo", TexMan.Type_MiscPatch, TEXTURE_FLAGS); + blockBigSel = TexMan.checkForTexture("gb_wpsel", TexMan.Type_MiscPatch, TEXTURE_FLAGS); + + // Sizes + ammoPipSize = TexMan.getScaledSize(ammoPip); + } + + transient TextureID circle; + transient TextureID halfCircle; + transient TextureID ammoPip; + transient TextureID hand; + transient TextureID pointer; + transient TextureID textBox; + transient TextureID noIcon; + + transient TextureID blockBox; + transient TextureID blockBig; + transient TextureID blockBigSel; + transient TextureID corner; + transient TextureID ammoLine; + + transient vector2 ammoPipSize; + + transient bool isLoaded; + + const TEXTURE_FLAGS = 0; + +} // class gb_TextureCache diff --git a/zscript/gearbox/view_model.zs b/zscript/gearbox/view_model.zs new file mode 100644 index 0000000000..ec326187af --- /dev/null +++ b/zscript/gearbox/view_model.zs @@ -0,0 +1,37 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2020-2021 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +struct gb_ViewModel +{ + + int selectedIndex; + + Array tags; + Array slots; + Array indices; + + Array icons; + Array iconScaleXs; + Array iconScaleYs; + + Array quantity1; + Array maxQuantity1; + + Array quantity2; + Array maxQuantity2; + +} // struct gb_ViewModel diff --git a/zscript/gearbox/weapon_data.zs b/zscript/gearbox/weapon_data.zs new file mode 100644 index 0000000000..99de501e63 --- /dev/null +++ b/zscript/gearbox/weapon_data.zs @@ -0,0 +1,24 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2020-2021 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +struct gb_WeaponData +{ + + Array< class > weapons; + Array slots; + +} // struct gb_WeaponData diff --git a/zscript/gearbox/weapon_data_loader.zs b/zscript/gearbox/weapon_data_loader.zs new file mode 100644 index 0000000000..25cd8f69d6 --- /dev/null +++ b/zscript/gearbox/weapon_data_loader.zs @@ -0,0 +1,154 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2020-2021 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +class gb_WeaponDataLoader play +{ + + /** + * Code is adapted from + * GZDoom::src/src/gzdoom/wadsrc/static/zscript/actors/player/player_cheat.zs::CheatGive. + */ + static + void load(out gb_WeaponData data) + { + int nClasses = AllActorClasses.Size(); + + gb_WeaponInfo info; + PlayerInfo player = players[consolePlayer]; + + for (int i = 0; i < nClasses; ++i) + { + let type = (class)(AllActorClasses[i]); + + if (type == NULL || type == "Weapon") continue; + + // This check from CheatGive doesn't work here. + // Why does it allow giving weapons for Treasure Tech there? + // Anyway, adding some replaced weapons to the data list won't harm, they + // won't show up unless the player has them. + //let replacement = Actor.getReplacement(type); + //if (replacement != type && !(replacement is "DehackedPickup")) continue; + + bool located; + int slot; + int priority; + [located, slot, priority] = player.weapons.LocateWeapon(type); + + if (!located) continue; + + readonly def = GetDefaultByType(type); + if (!def.CanPickup(player.mo)) continue; + + info.push(type, slot, priority); + } + + sortWeapons(info); + + data.weapons.move(info.classes); + data.slots.move(info.slots); + } + +// private: //////////////////////////////////////////////////////////////////////////////////////// + + private static + void sortWeapons(gb_WeaponInfo info) + { + int nWeapons = info.classes.size(); + + quickSortWeapons(info, 0, nWeapons - 1); + } + + private static + void quickSortWeapons(gb_WeaponInfo info, int lo, int hi) + { + if (lo < hi) + { + int p = quickSortWeaponsPartition(info, lo, hi); + quickSortWeapons(info, lo, p - 1); + quickSortWeapons(info, p + 1, hi ); + } + } + + private static + int quickSortWeaponsPartition(gb_WeaponInfo info, int lo, int hi) + { + int pivot = measure(info, hi); + int i = lo - 1; + + for (int j = lo; j <= hi - 1; ++j) + { + if (measure(info, j) <= pivot) + { + ++i; + info.swap(i, j); + } + } + info.swap(i + 1, hi); + + return i + 1; + } + + private static + int measure(gb_WeaponInfo info, int index) + { + int slot = info.slots[index]; + if (slot == 0) slot = 99; + + int result = slot * 100 + info.priorities[index]; + return result; + } + +} // class gb_WeaponDataLoader + +struct gb_WeaponInfo +{ + +// public: ///////////////////////////////////////////////////////////////////////////////////////// + + void push(class aClass, int slot, int priority) + { + classes .push(aClass); + slots .push(slot); + priorities.push(priority); + } + + void swap(int i, int j) + { + { + let tmp = classes[i]; + classes[i] = classes[j]; + classes[j] = tmp; + } + { + int tmp = slots[i]; + slots[i] = slots[j]; + slots[j] = tmp; + } + { + int tmp = priorities[i]; + priorities[i] = priorities[j]; + priorities[j] = tmp; + } + } + +// public: ///////////////////////////////////////////////////////////////////////////////////////// + + Array< class > classes; + Array slots; + Array priorities; + +} // struct gb_WeaponInfo diff --git a/zscript/gearbox/weapon_menu.zs b/zscript/gearbox/weapon_menu.zs new file mode 100644 index 0000000000..48239fb9a9 --- /dev/null +++ b/zscript/gearbox/weapon_menu.zs @@ -0,0 +1,444 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2020-2021 + * Carrascado 2022 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +class gb_WeaponMenu +{ + + static + gb_WeaponMenu from(gb_WeaponData weaponData, gb_Options options) + { + let result = new("gb_WeaponMenu"); + + result.mWeapons.move(weaponData.weapons); + result.mSlots.move(weaponData.slots); + result.mSelectedIndex = 0; + result.mCacheTime = 0; + result.mOptions = options; + + loadIconServices(result.mIconServices); + loadHideServices(result.mHideServices); + + return result; + } + + int getSelectedIndex() const + { + return mSelectedIndex; + } + + bool setSelectedIndexFromView(gb_ViewModel viewModel, int index) + { + if (index == -1 || mSelectedIndex == viewModel.indices[index]) return false; + + mSelectedIndex = viewModel.indices[index]; + return true; + } + + void setSelectedWeapon(class aClass) + { + if (aClass == NULL) return; + + uint index = getIndexOf(aClass); + if (index != mWeapons.size()) mSelectedIndex = index; + } + + ui + bool selectNextWeapon() + { + mSelectedIndex = findNextWeapon(); + return mSelectedIndex != mWeapons.size(); + } + + ui + bool selectPrevWeapon() + { + mSelectedIndex = findPrevWeapon(); + return mSelectedIndex != mWeapons.size(); + } + + bool selectSlot(int slot, bool selectFirstWeapon = false) + { + uint nWeapons = mWeapons.size(); + int direction = mOptions.isSlotCycleOrderReversed() ? -1 : 1; + int startOffset = selectFirstWeapon ? 0 : (mSelectedIndex + direction); + + for (uint i = 0; i < nWeapons; ++i) + { + uint index = (startOffset + nWeapons + direction * i) % nWeapons; + if (mSlots[index] == slot && isInInventory(index) && !isHidden(mWeapons[index].getClassName())) + { + mSelectedIndex = index; + return true; + } + } + + return false; + } + + bool isOneWeaponInSlot(int slot) const + { + uint nWeapons = mWeapons.size(); + int nWeaponsInSlot = 0; + for (uint i = 1; i < nWeapons; ++i) + { + uint index = (mSelectedIndex + nWeapons - i) % nWeapons; + nWeaponsInSlot += (mSlots[index] == slot && isInInventory(index)); + if (nWeaponsInSlot > 1) return false; + } + if (nWeaponsInSlot == 0) return false; + return true; + } + + bool isInInventory(int index) const + { + return NULL != players[consolePlayer].mo.findInventory(mWeapons[index]); + } + + string confirmSelection() const + { + if (mSelectedIndex >= mWeapons.size()) return ""; + + return getDefaultByType(mWeapons[mSelectedIndex]).getClassName(); + } + + ui + void fill(out gb_ViewModel viewModel) + { + // every other tic. + bool isCacheValid = (level.time <= mCacheTime + 1); + if (!isCacheValid) + { + mCacheTime = level.time; + mCachedViewModel.tags .clear(); + mCachedViewModel.slots .clear(); + mCachedViewModel.indices .clear(); + mCachedViewModel.icons .clear(); + mCachedViewModel.iconScaleXs .clear(); + mCachedViewModel.iconScaleYs .clear(); + mCachedViewModel.quantity1 .clear(); + mCachedViewModel.maxQuantity1.clear(); + mCachedViewModel.quantity2 .clear(); + mCachedViewModel.maxQuantity2.clear(); + + fillDirect(mCachedViewModel); + } + + copy(mCachedViewModel, viewModel); + } + + void rotatePriority() + { + mSelectedIndex = rotatePriorityForIndex(mSelectedIndex); + } + + uint rotatePriorityForIndex(int index) + { + bool isIndexFound; + uint targetIndex; + [isIndexFound, targetIndex] = findIndexOfNextWeaponWithSlot(index, mSlots[index]); + + if (!isIndexFound) return mSelectedIndex; + + rotate(index, targetIndex); + return targetIndex; + } + + void rotateSlot() + { + mSelectedIndex = rotateSlotForIndex(mSelectedIndex); + } + + int rotateSlotForIndex(int oldIndex) + { + uint nWeapons = mWeapons.size(); + if (nWeapons < 2) return oldIndex; + + int oldSlot = mSlots[oldIndex]; + int newSlot = getNextSlot(oldSlot); + + uint i = 0; + for (int slot = 1;;) + { + while (i < nWeapons && mSlots[i] == slot) ++i; + + if (slot == newSlot) + { + mSlots[oldIndex] = newSlot; + move(oldIndex, i); + return (oldIndex < i) ? i - 1 : i; + } + + slot = getNextSlot(slot); + if (slot == 1) break; + } + + return oldIndex; + } + + bool isThereNoWeapons() const + { + bool isNothingFound = true; + uint nWeapons = mWeapons.size(); + for (uint i = 0; i < nWeapons; ++i) + { + if (isInInventory(i) && !isHidden(mWeapons[i].getClassName())) return false; + } + return isNothingFound; + } + +// private: //////////////////////////////////////////////////////////////////////////////////////// + + private + void rotate(uint oldIndex, uint newIndex) + { + if (oldIndex > newIndex) move(oldIndex, newIndex); + else move(oldIndex, newIndex + 1); + } + + private + void move(uint oldIndex, uint newIndex) + { + if (oldIndex == newIndex) return; + + mWeapons.insert(newIndex, mWeapons[oldIndex]); + mSlots .insert(newIndex, mSlots [oldIndex]); + + if (newIndex < oldIndex) ++oldIndex; + + mWeapons.delete(oldIndex); + mSlots .delete(oldIndex); + } + + private + bool, uint findIndexOfNextWeaponWithSlot(uint weaponIndex, int slot) + { + uint nWeapons = mWeapons.size(); + for (uint i = 1; i < nWeapons; ++i) + { + uint targetCandidateIndex = (weaponIndex + i) % nWeapons; + if (mSlots[targetCandidateIndex] == slot) + { + return true, targetCandidateIndex; + } + } + + return false, 0; + } + + private static + int getNextSlot(int slot) + { + if (1 <= slot && slot <= 8) return slot + 1; + + switch (slot) + { + case 9: return 0; + case 0: return 10; + case 10: return 11; + case 11: return 1; + } + + Console.printf("Unexpected slot: %d.", slot); + return 1; + } + + private static ui + void copy(gb_ViewModel source, out gb_ViewModel destination) + { + destination.selectedIndex = source.selectedIndex; + + destination.tags .copy(source.tags); + destination.slots .copy(source.slots); + destination.indices .copy(source.indices); + destination.icons .copy(source.icons); + destination.iconScaleXs .copy(source.iconScaleXs); + destination.iconScaleYs .copy(source.iconScaleYs); + destination.quantity1 .copy(source.quantity1); + destination.maxQuantity1.copy(source.maxQuantity1); + destination.quantity2 .copy(source.quantity2); + destination.maxQuantity2.copy(source.maxQuantity2); + } + + private ui + void fillDirect(out gb_ViewModel viewModel) + { + uint nWeapons = mWeapons.size(); + for (uint i = 0; i < nWeapons; ++i) + { + let aWeapon = Weapon(players[consolePlayer].mo.findInventory(mWeapons[i])); + + if (aWeapon == NULL) + { + if (mOptions.isPositionLocked()) + { + viewModel.tags .push(""); + viewModel.slots .push(mSlots[i]); + viewModel.indices .push(i); + viewModel.icons .push(texman.checkfortexture("TNT1A0"));//-1); + viewModel.iconScaleXs .push(-1); + viewModel.iconScaleYs .push(-1); + viewModel.quantity1 .push(-1); + viewModel.maxQuantity1.push(-1); + viewModel.quantity2 .push(-1); + viewModel.maxQuantity2.push(-1); + } + continue; + } + + if (isHidden(aWeapon.getClassName())) continue; + + if (mSelectedIndex == i) viewModel.selectedIndex = viewModel.tags.size(); + + viewModel.tags.push(aWeapon.getTag()); + viewModel.slots.push(mSlots[i]); + viewModel.indices.push(i); + + TextureID icon = getIconFor(aWeapon); + + // Workaround, casting TextureID to int may be unreliable. + viewModel.icons.push(icon);//(int(icon)); + viewModel.iconScaleXs.push(aWeapon.scale.x); + viewModel.iconScaleYs.push(aWeapon.scale.y); + + bool hasAmmo1 = aWeapon.ammo1; + bool hasAmmo2 = aWeapon.ammo2 && aWeapon.ammo2 != aWeapon.ammo1; + + viewModel. quantity1.push(hasAmmo1 ? aWeapon.ammo1. amount : -1); + viewModel.maxQuantity1.push(hasAmmo1 ? aWeapon.ammo1.maxAmount : -1); + viewModel. quantity2.push(hasAmmo2 ? aWeapon.ammo2. amount : -1); + viewModel.maxQuantity2.push(hasAmmo2 ? aWeapon.ammo2.maxAmount : -1); + } + } + + private play + bool isHidden(string className) const + { + bool result = false; + + uint nServices = mHideServices.size(); + for (uint i = 0; i < nServices; ++i) + { + let service = mHideServices[i]; + string hideResponse = service.get(className); + if (hideResponse.length() != 0) + { + bool isHidden = hideResponse.byteAt(0) - 48; // convert to bool from "0" or "1". + result = isHidden; + } + } + + return result; + } + + private ui + TextureID getIconFor(Weapon aWeapon) const + { + TextureID icon = BaseStatusBar.getInventoryIcon(aWeapon, BaseStatusBar.DI_AltIconFirst); + + { + uint nServices = mIconServices.size(); + string className = aWeapon.getClassName(); + for (uint i = 0; i < nServices; ++i) + { + let service = mIconServices[i]; + string iconResponse = service.uiGet(className); + if (iconResponse.length() != 0) + { + TextureID iconFromService = TexMan.checkForTexture(iconResponse, TexMan.Type_Any); + if (iconFromService.isValid()) icon = iconFromService; + } + } + } + + return icon; + } + + private play State getReadyState(Weapon w) const { return w.getReadyState(); } + + private ui uint findNextWeapon() const { return findWeapon( 1); } + private ui uint findPrevWeapon() const { return findWeapon(-1); } + + /** + * @param direction search direction from the selected weapon: 1 or -1. + * + * @returns a weapon in the direction. If there is only one weapon, return + * it. If there are no weapons, return weapons number. + */ + private ui + uint findWeapon(int direction) const + { + uint nWeapons = mWeapons.size(); + // Note range: [1; nWeapons + 1) instead of [0; nWeapons). + // This is because I want the current weapon to be found last. + for (uint i = 1; i < nWeapons + 1; ++i) + { + uint index = (mSelectedIndex + i * direction + nWeapons) % nWeapons; + if (isHidden(mWeapons[index].getClassName())) continue; + if (isInInventory(index)) return index; + } + + return nWeapons; + } + + private + uint getIndexOf(class aClass) const + { + uint nWeapons = mWeapons.size(); + for (uint i = 0; i < nWeapons; ++i) + { + if (mWeapons[i] == aClass) return i; + } + return nWeapons; + } + + private static + void loadIconServices(out Array services) + { + loadServices("gb_IconService", services); + } + + private static + void loadHideServices(out Array services) + { + loadServices("gb_HideService", services); + } + + private static + void loadServices(string serviceName, out Array services) + { + let iterator = gb_ServiceIterator.find(serviceName); + gb_Service aService; + while (aService = iterator.next()) + { + services.push(aService); + } + } + + private Array< class > mWeapons; + private Array< int > mSlots; + private uint mSelectedIndex; + + private Array mIconServices; + private Array mHideServices; + + private gb_ViewModel mCachedViewModel; + private int mCacheTime; + + private gb_Options mOptions; + +} // class gb_WeaponMenu diff --git a/zscript/gearbox/wheel/controller.zs b/zscript/gearbox/wheel/controller.zs new file mode 100644 index 0000000000..acbabc262e --- /dev/null +++ b/zscript/gearbox/wheel/controller.zs @@ -0,0 +1,102 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2020-2021 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +class gb_WheelController +{ + + static + gb_WheelController from(gb_Options options, gb_Screen screen) + { + let result = new("gb_WheelController"); + + result.reset(); + result.mScreen = screen; + result.mOptions = options; + + return result; + } + + void reset() + { + mX = 0; + mY = 0; + + PlayerInfo player = players[consolePlayer]; + mStartPitch = player.mo.pitch; + mStartYaw = player.mo.angle; + } + + void fill(out gb_WheelControllerModel model) + { + model.angle = getAngle(); + model.radius = getRadius(); + } + + play + void process() const + { + if (!mOptions.isMouseInWheel()) return; + + vector2 mouseSensitivity = mOptions.getMouseSensitivity(); + double yawMod = 0.08 * mouseSensitivity.x; + double pitchMod = 0.08 * mouseSensitivity.y; + + PlayerInfo player = players[consolePlayer]; + // Code derived from PyWeaponWheel's mouse input handling. + mX -= int(round(player.original_cmd.yaw * yawMod )); + mY -= int(round(player.original_cmd.pitch * pitchMod)); + + if (multiplayer) + { + gb_Sender.sendPlayerAngles(mStartPitch, mStartYaw); + } + else + { + gb_Changer.setAngles(players[consolePlayer], mStartPitch, mStartYaw); + } + + vector2 center = mScreen.getWheelCenter(); + int centerX = int(center.x); + int centerY = int(center.y); + mX = clamp(mX, -centerX, Screen.getWidth() - centerX); + mY = clamp(mY, -centerY, Screen.getHeight() - centerY); + } + +// private: //////////////////////////////////////////////////////////////////////////////////////// + + private + double getAngle() const + { + return -atan2(mX, mY) + 180; + } + + private + double getRadius() const + { + return sqrt(mX * mX + mY * mY); + } + + private int mX; + private int mY; + + private double mStartPitch; + private double mStartYaw; + + private gb_Screen mScreen; + private gb_Options mOptions; + +} // class gb_WheelController diff --git a/zscript/gearbox/wheel/controller_model.zs b/zscript/gearbox/wheel/controller_model.zs new file mode 100644 index 0000000000..82295b7a6f --- /dev/null +++ b/zscript/gearbox/wheel/controller_model.zs @@ -0,0 +1,24 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2020-2021 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +struct gb_WheelControllerModel +{ + + double angle; + double radius; + +} // struct gb_WheelControllerModel diff --git a/zscript/gearbox/wheel/indexer.zs b/zscript/gearbox/wheel/indexer.zs new file mode 100644 index 0000000000..a05183b4ff --- /dev/null +++ b/zscript/gearbox/wheel/indexer.zs @@ -0,0 +1,237 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2020-2021 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +class gb_WheelIndexer +{ + + static + gb_WheelIndexer from(gb_MultiWheelMode multiWheelMode, gb_Screen screen) + { + let result = new("gb_WheelIndexer"); + result.mSelectedIndex = UNDEFINED_INDEX; + result.mLastSlotIndex = UNDEFINED_INDEX; + result.mInnerIndex = UNDEFINED_INDEX; + result.mOuterIndex = UNDEFINED_INDEX; + result.mMultiWheelMode = multiWheelMode; + result.mScreen = screen; + return result; + } + + int getSelectedIndex() const { return mSelectedIndex; } + + int getInnerIndex(int externalSelectedIndex, gb_ViewModel viewModel) const + { + if (areIndicesDefined()) return mInnerIndex; + + uint nWeapons = viewModel.tags.size(); + uint externalSelectedIndexInModel = UNDEFINED_INDEX; + for (uint i = 0; i < nWeapons; ++i) + { + if (viewModel.indices[i] == externalSelectedIndex) + { + externalSelectedIndexInModel = i; + break; + } + } + + if (!mMultiWheelMode.isEngaged(viewModel)) return externalSelectedIndexInModel; + + gb_MultiWheelModel multiWheelModel; + gb_MultiWheel.fill(viewModel, multiWheelModel); + + uint nPlaces = multiWheelModel.data.size(); + for (uint i = 0; i < nPlaces; ++i) + { + if (multiWheelModel.isWeapon[i]) + { + if (viewModel.indices[multiWheelModel.data[i]] == externalSelectedIndex) return i; + } + else + { + if (multiWheelModel.data[i] == viewModel.slots[externalSelectedIndexInModel]) return i; + } + } + + return UNDEFINED_INDEX; + } + + int getOuterIndex(int externalSelectedIndex, gb_ViewModel viewModel) const + { + if (areIndicesDefined()) return mOuterIndex; + + if (!mMultiWheelMode.isEngaged(viewModel)) return UNDEFINED_INDEX; + + uint nWeapons = viewModel.tags.size(); + uint externalSelectedIndexInModel = UNDEFINED_INDEX; + for (uint i = 0; i < nWeapons; ++i) + { + if (viewModel.indices[i] == externalSelectedIndex) + { + externalSelectedIndexInModel = i; + break; + } + } + + int slot = viewModel.slots[externalSelectedIndexInModel]; + + uint start = 0; + for (; start < nWeapons && viewModel.slots[start] != slot; ++start); + + return externalSelectedIndexInModel - start; + } + + void update(gb_ViewModel viewModel, gb_WheelControllerModel controllerModel) + { + if (controllerModel.radius < mScreen.getWheelDeadRadius()) + { + mSelectedIndex = UNDEFINED_INDEX; + mLastSlotIndex = UNDEFINED_INDEX; + mInnerIndex = UNDEFINED_INDEX; + mOuterIndex = UNDEFINED_INDEX; + return; + } + + uint nWeapons = viewModel.tags.size(); + + if (!mMultiWheelMode.isEngaged(viewModel)) + { + mSelectedIndex = gb_WheelInnerIndexer.getSelectedIndex(nWeapons, controllerModel, mScreen); + mLastSlotIndex = UNDEFINED_INDEX; + mInnerIndex = mSelectedIndex; + mOuterIndex = UNDEFINED_INDEX; + return; + } + + gb_MultiWheelModel multiWheelModel; + gb_MultiWheel.fill(viewModel, multiWheelModel); + + uint nPlaces = multiWheelModel.data.size(); + int wheelRadius = mScreen.getWheelRadius(); + + if (controllerModel.radius < wheelRadius) + { + reportInnerIndex(nPlaces, controllerModel, multiWheelModel, viewModel); + return; + } + + if (mLastSlotIndex == UNDEFINED_INDEX || mLastSlotIndex >= multiWheelModel.data.size()) + { + mSelectedIndex = UNDEFINED_INDEX; + mInnerIndex = UNDEFINED_INDEX; + mOuterIndex = UNDEFINED_INDEX; + return; + } + + int slot = multiWheelModel.data[mLastSlotIndex]; + + uint start = 0; + for (; start < nWeapons && viewModel.slots[start] != slot; ++start); + uint end = start; + for (; end < nWeapons && viewModel.slots[end] == slot; ++end); + uint nWeaponsInSlot = end - start; + + double slotAngle = itemAngle(nPlaces, mLastSlotIndex); + + double r = controllerModel.radius; + double w = wheelRadius; + double forSlotAngle = slotAngle - controllerModel.angle; + double side = sqrt(r * r + w * w - 2 * r * w * cos(forSlotAngle)); + + if (side < mScreen.getWheelDeadRadius()) + { + reportInnerIndex(nPlaces, controllerModel, multiWheelModel, viewModel); + return; + } + + // due to computation precision, the value of ratio may be slightly out of range [-1, 1]. + double ratio = clamp(r / side * sin(forSlotAngle), -1.0, 1.0); + double angle = -asin(ratio); + + // Limit angle on the borders of the second wheel: + double borderRadius = wheelRadius / sin(90 - forSlotAngle); + if (r < borderRadius) + { + angle = (angle > 0) ? 89.9999 : -89.9999; + } + + angle += 90; + angle %= 180; + + int indexInSlot = int((angle * nWeaponsInSlot / 180.0) % nWeaponsInSlot); + + mSelectedIndex = start + indexInSlot; + mInnerIndex = mLastSlotIndex; + mOuterIndex = indexInSlot; + } + +// private: //////////////////////////////////////////////////////////////////////////////////////// + + private + void reportInnerIndex( uint nPlaces + , gb_WheelControllerModel controllerModel + , gb_MultiWheelModel multiWheelModel + , gb_ViewModel viewModel + ) + { + int innerIndex = gb_WheelInnerIndexer.getSelectedIndex(nPlaces, controllerModel, mScreen); + bool isWeapon = multiWheelModel.isWeapon[innerIndex]; + + mSelectedIndex = isWeapon + ? multiWheelModel.data[innerIndex] + : firstInSlot(viewModel, multiWheelModel.data[innerIndex]); + + mLastSlotIndex = isWeapon ? UNDEFINED_INDEX : innerIndex; + mInnerIndex = innerIndex; + mOuterIndex = 0; + } + + private static + int firstInSlot(gb_ViewModel viewModel, int slot) + { + int nWeapons = viewModel.tags.size(); + for (int i = 0; i < nWeapons; ++i) + { + if (viewModel.slots[i] == slot) return i; + } + + return UNDEFINED_INDEX; + } + + private + bool areIndicesDefined() const + { + return (mInnerIndex != UNDEFINED_INDEX); + } + + private static + double itemAngle(uint nItems, uint index) + { + return 360.0 / nItems * index; + } + + const UNDEFINED_INDEX = -1; + + private int mSelectedIndex; + + private int mLastSlotIndex; + private int mInnerIndex; + private int mOuterIndex; + + private gb_MultiWheelMode mMultiWheelMode; + private gb_Screen mScreen; + +} // class gb_WheelIndexer diff --git a/zscript/gearbox/wheel/inner_indexer.zs b/zscript/gearbox/wheel/inner_indexer.zs new file mode 100644 index 0000000000..91bc528f44 --- /dev/null +++ b/zscript/gearbox/wheel/inner_indexer.zs @@ -0,0 +1,33 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2020-2021 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +class gb_WheelInnerIndexer +{ + + static + int getSelectedIndex(uint nItems, gb_WheelControllerModel controllerModel, gb_Screen screen) + { + if (controllerModel.radius < screen.getWheelDeadRadius() || nItems == 0) + { + return -1; + } + + int result = int(round(controllerModel.angle * nItems / 360.0)) % nItems; + return result; + } + +} // class gb_WheelInnerIndexer diff --git a/zscript/gearbox/wheel/multiwheel.zs b/zscript/gearbox/wheel/multiwheel.zs new file mode 100644 index 0000000000..da011635e8 --- /dev/null +++ b/zscript/gearbox/wheel/multiwheel.zs @@ -0,0 +1,66 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2020-2021 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +struct gb_MultiWheelModel +{ + Array isWeapon; + Array data; // slot or weapon index +} + +class gb_MultiWheel +{ + + static + void fill(gb_ViewModel viewModel, out gb_MultiWheelModel model) + { + uint nWeapons = viewModel.tags.size(); + int lastSlot = -1; + + for (uint i = 0; i < nWeapons; ++i) + { + int slot = viewModel.slots[i]; + bool isSingle = isSingleWeaponInSlot(viewModel, nWeapons, slot); + if (isSingle) + { + model.isWeapon.push(true); + model.data.push(i); + } + else + { + if (slot != lastSlot) + { + lastSlot = slot; + model.isWeapon.push(false); + model.data.push(slot); + } + } + } + } + + static + bool isSingleWeaponInSlot(gb_ViewModel viewModel, uint nWeapons, int slot) + { + int nWeaponsInSlot = 0; + for (uint i = 0; i < nWeapons; ++i) + { + nWeaponsInSlot += (viewModel.slots[i] == slot); + if (nWeaponsInSlot > 1) return false; + } + return true; + } + +} // class gb_MultiWheel diff --git a/zscript/gearbox/wheel/multiwheel_mode.zs b/zscript/gearbox/wheel/multiwheel_mode.zs new file mode 100644 index 0000000000..b9080c33d3 --- /dev/null +++ b/zscript/gearbox/wheel/multiwheel_mode.zs @@ -0,0 +1,40 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2020-2021 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +class gb_MultiWheelMode +{ + + static + gb_MultiWheelMode from(gb_Options options) + { + let result = new("gb_MultiWheelMode"); + result.mOptions = options; + return result; + } + + bool isEngaged(gb_ViewModel viewModel) + { + uint nWeapons = viewModel.tags.size(); + bool result = (nWeapons > mOptions.getMultiWheelLimit()); + return result; + } + +// private: //////////////////////////////////////////////////////////////////////////////////////// + + private gb_Options mOptions; + +} // class gb_MultiWheelMode diff --git a/zscript/gearbox/wheel/screen.zs b/zscript/gearbox/wheel/screen.zs new file mode 100644 index 0000000000..64372d9749 --- /dev/null +++ b/zscript/gearbox/wheel/screen.zs @@ -0,0 +1,69 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2020-2021 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +/** + * This class provides helper functions for screen position and sizes. + */ +class gb_Screen +{ + + static + gb_Screen from(gb_Options options) + { + let result = new("gb_Screen"); + result.mOptions = options; + return result; + } + + vector2 getWheelCenter() const + { + int screenWidth = Screen.getWidth(); + int halfScreenHeight = Screen.getHeight() / 2; + int centerPosition = int((screenWidth / 2 - halfScreenHeight) * mOptions.getWheelPosition()); + return (screenWidth / 2 + centerPosition, halfScreenHeight); + } + + int getWheelRadius() const + { + return getScaledScreenHeight() / 4; + } + + int getWheelDeadRadius() const + { + return getScaledScreenHeight() / 16; + } + + double getScaleFactor() const + { + return getScaledScreenHeight() / 1080.0; + } + + int getScaledScreenHeight() const + { + return int(Screen.getHeight() * mOptions.getWheelScale()); + } + + int getScaledScreenWidth() const + { + return int(Screen.getWidth() * mOptions.getWheelScale()); + } + +// private: //////////////////////////////////////////////////////////////////////////////////////// + + private gb_Options mOptions; + +} // class gb_Screen diff --git a/zscript/gearbox/wheel/text.zs b/zscript/gearbox/wheel/text.zs new file mode 100644 index 0000000000..13fdb7c2f9 --- /dev/null +++ b/zscript/gearbox/wheel/text.zs @@ -0,0 +1,137 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2021 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +/** + * This class helps displaying text on screen. + */ +class gb_Text +{ + + static + gb_Text from(gb_TextureCache textureCache, gb_Screen screen, gb_FontSelector fontSelector) + { + let result = new("gb_Text"); + result.mTextureCache = textureCache; + result.mScreen = screen; + result.mFontSelector = fontSelector; + return result; + } + + static + void draw(string aString, vector2 pos, Font aFont, double alpha, bool isBig = false, int fontcol = font.CR_WHITE) + { + int textScale = isBig ? getBigTextScale() : getTextScale(); + + pos.x -= aFont.stringWidth(aString) * textScale / 2; + pos.y -= aFont.getHeight() * textScale / 2; + + Screen.drawText( aFont + , fontcol //added to change the color for each specific call instead of just generally + , pos.x + , pos.y + , aString + , DTA_Alpha , alpha + , DTA_ScaleX , textScale + , DTA_ScaleY , textScale + ); + } + + void drawBox( string topText + , string middleText + , string bottomText + , vector2 pos + , bool isPosTop // true: pos is the top of text box, false: bottom. + , color baseColor + , double alpha, + int fontcol = font.CR_WHITE, //added to change the color for each specific call instead of just generally + bool novertscale = false //dont adjust the vertical scale of the box + ) + { + double scaleFactor = mScreen.getScaleFactor(); + Font aFont = mFontSelector.getFont(); + int textScale = getTextScale(); + int lineHeight = aFont.getHeight() * textScale; + int margin = int(10 * scaleFactor); + int height = int((margin * 2 + lineHeight * 3) / scaleFactor); + + if (!isPosTop) pos.y -= height * scaleFactor; + + int topTextWidth = aFont.stringWidth(topText); + int middleTextWidth = aFont.stringWidth(middleText); + int bottomTextWidth = aFont.stringWidth(bottomText); + + int width = max(int( ( max(middleTextWidth, max(topTextWidth, bottomTextWidth)) * textScale + + margin * 2 + ) / scaleFactor + ), height * 2); + vector2 size = (width, height) * scaleFactor; + vector2 center = pos + (0, size.y / 2); + + if(novertscale) + { + Screen.drawTexture( mTextureCache.textBox + , NO_ANIMATION + , center.x + , center.y + //, DTA_FillColor , baseColor + , DTA_AlphaChannel , true + , DTA_CenterOffset , true + , DTA_Alpha , alpha + , DTA_DestWidth , int(size.x) + ); + } + else + { + Screen.drawTexture( mTextureCache.textBox + , NO_ANIMATION + , center.x + , center.y + //, DTA_FillColor , baseColor + , DTA_AlphaChannel , true + , DTA_CenterOffset , true + , DTA_Alpha , alpha + , DTA_DestWidth , int(size.x) + , DTA_DestHeight , int(size.y) //aaa + ); + } + vector2 line = (0, lineHeight); + if (topText .length() > 0) draw(topText , center - line, aFont, alpha,fontcol:fontcol); + if (middleText.length() > 0) draw(middleText, center , aFont, alpha,fontcol:fontcol); + if (bottomText.length() > 0) draw(bottomText, center + line, aFont, alpha,fontcol:fontcol); + } + +// private: //////////////////////////////////////////////////////////////////////////////////////// + + private static + int getBigTextScale() + { + return getTextScale() * 2; + } + + private static + int getTextScale() + { + return max(Screen.getHeight() / 720, 1); + } + + const NO_ANIMATION = 0; // == false + + private gb_TextureCache mTextureCache; + private gb_Screen mScreen; + private gb_FontSelector mFontSelector; + +} // class gb_Text diff --git a/zscript/gearbox/wheel/view.zs b/zscript/gearbox/wheel/view.zs new file mode 100644 index 0000000000..8b0161fcb7 --- /dev/null +++ b/zscript/gearbox/wheel/view.zs @@ -0,0 +1,577 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2020-2021 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +class gb_WheelView +{ + + static + gb_WheelView from( gb_Options options + , gb_MultiWheelMode multiWheelMode + , gb_TextureCache textureCache + , gb_Screen screen + , gb_FontSelector fontSelector + ) + { + let result = new("gb_WheelView"); + + result.setAlpha(1.0); + result.setBaseColor(0x2222CC); + + result.mScreen = screen; + result.mOptions = options; + result.mFontSelector = fontSelector; + result.mMultiWheelMode = multiWheelMode; + result.mText = gb_Text.from(textureCache, screen, fontSelector); + result.mTextureCache = textureCache; + result.mIsRotating = true; + + return result; + } + + void setAlpha(double alpha) + { + mAlpha = alpha; + } + + void setBaseColor(int color) + { + mBaseColor = color; + } + + void setRotating(bool isRotating) + { + mIsRotating = isRotating; + } + + void display( gb_ViewModel viewModel + , gb_WheelControllerModel controllerModel + , bool showPointer + , int innerIndex + , int outerIndex + ) const + { + mScaleFactor = mScreen.getScaleFactor(); + mCenter = mScreen.getWheelCenter(); + + drawInnerWheel(); + + uint nWeapons = viewModel.tags.size(); + if (nWeapons == 0) return; + + int screenHeight = mScreen.getScaledScreenHeight(); + int radius = screenHeight * 5 / 32; + int allowedWidth = int(screenHeight * 3 / 16 - MARGIN * 2 * mScaleFactor); + + int nPlaces = 0; + + bool isSlotExpanded = false; + bool isMultiWheelMode = mMultiWheelMode.isEngaged(viewModel); + if (isMultiWheelMode) + { + gb_MultiWheelModel multiWheelModel; + gb_MultiWheel.fill(viewModel, multiWheelModel); + + nPlaces = multiWheelModel.data.size(); + for (uint i = 0; i < nPlaces; ++i) + { + bool isWeapon = multiWheelModel.isWeapon[i]; + int data = multiWheelModel.data[i]; + + if (isWeapon) displayWeapon(i, data, nPlaces, radius, allowedWidth, viewModel, mCenter); + else displaySlot (i, data, nPlaces, radius); + } + + drawHands(nPlaces, innerIndex, mCenter, 0); + + isSlotExpanded = (outerIndex != UNDEFINED_INDEX && !multiWheelModel.isWeapon[innerIndex]); + if (isSlotExpanded) + { + int slot = multiWheelModel.data[innerIndex]; + drawOuterWeapons( innerIndex + , outerIndex + , nPlaces + , slot + , viewModel + , radius + , allowedWidth + , int(controllerModel.radius) + ); + } + } + else + { + isSlotExpanded = false; + for (uint i = 0; i < nWeapons; ++i) + { + if (i == innerIndex) continue; + displayWeapon(i, i, nWeapons, radius, allowedWidth, viewModel, mCenter); + } + + nPlaces = nWeapons; + if (innerIndex != -1) + { + displayWeapon(innerIndex, innerIndex, nPlaces, radius, allowedWidth, viewModel, mCenter); + } + drawHands(nPlaces, innerIndex, mCenter, 0); + } + + if (showPointer) + { + drawPointer(controllerModel.angle, controllerModel.radius); + } + + if (mOptions.isShowingTags()) + { + drawWeaponDescription(viewModel, innerIndex, nPlaces, isSlotExpanded); + } + } + +// private: //////////////////////////////////////////////////////////////////////////////////////// + + private + void drawOuterWeapons( int innerIndex + , int outerIndex + , uint nPlaces + , int slot + , gb_ViewModel viewModel + , int radius + , int allowedWidth + , int controllerRadius + ) + { + int wheelRadius = mScreen.getWheelRadius(); + double angle = itemAngle(nPlaces, innerIndex); + vector2 outerWheelCenter = (sin(angle), -cos(angle)) * wheelRadius + mCenter; + drawOuterWheel(outerWheelCenter.x, outerWheelCenter.y, -angle); + + uint nWeapons = viewModel.tags.size(); + + uint start = 0; + for (; start < nWeapons && viewModel.slots[start] != slot; ++start); + uint end = start; + for (; end < nWeapons && viewModel.slots[end] == slot; ++end); + + uint nWeaponsInSlot = end - start; + double startingAngle = angle - 90 + (180.0 / nWeaponsInSlot / 2); + + uint place = 0; + uint selectedPlace = 0; + for (uint i = start; i < end; ++i, ++place) + { + if (i == viewModel.selectedIndex) + { + selectedPlace = place; + continue; + } + + displayWeapon( place + , i + , nWeaponsInSlot * 2 + , radius + , allowedWidth + , viewModel + , outerWheelCenter + , startingAngle + ); + } + + // draw the selected thing last in case of icon overlapping + displayWeapon( selectedPlace + , viewModel.selectedIndex + , nWeaponsInSlot * 2 + , radius + , allowedWidth + , viewModel + , outerWheelCenter + , startingAngle + ); + + int deadRadius = mScreen.getWheelDeadRadius(); + + drawHands( nWeaponsInSlot * 2 + , outerIndex + , outerWheelCenter + , -startingAngle + ); + } + + private + void drawInnerWheel() + { + int wheelDiameter = mScreen.getWheelRadius() * 2; + Screen.drawTexture( mTextureCache.circle + , NO_ANIMATION + , mCenter.x + , mCenter.y + //, DTA_FillColor , mBaseColor + , DTA_AlphaChannel , true + , DTA_Alpha , mAlpha + , DTA_CenterOffset , true + , DTA_DestWidth , wheelDiameter + , DTA_DestHeight , wheelDiameter + ); + } + + private + void drawOuterWheel(double x, double y, double angle) + { + int wheelDiameter = mScreen.getWheelRadius() * 2; + Screen.drawTexture( mTextureCache.halfCircle + , NO_ANIMATION + , x + , y + //, DTA_FillColor , mBaseColor + , DTA_AlphaChannel , true + , DTA_Alpha , mAlpha + , DTA_CenterOffset , true + , DTA_Rotate , angle + , DTA_DestWidth , wheelDiameter + , DTA_DestHeight , wheelDiameter + ); + } + + private + void displayWeapon( uint place + , uint iconIndex + , uint nPlaces + , int radius + , int allowedWidth + , gb_ViewModel viewModel + , vector2 center + , double startingAngle = 0.0 + ) const + { + // Code is adapted from GZDoom AltHud.DrawImageToBox. + + TextureID texture = viewModel.icons[iconIndex]; + vector2 textureSize = TexMan.getScaledSize(texture) * 2 * mScaleFactor; + + if (texture.isValid()) + { + textureSize.x *= viewModel.iconScaleXs[iconIndex]; + textureSize.y *= viewModel.iconScaleYs[iconIndex]; + } + + bool isTall = (textureSize.y * 1.2 > textureSize.x); + + double scale = isTall + ? ((allowedWidth < textureSize.y) ? allowedWidth / textureSize.y : 1.0) + : ((allowedWidth < textureSize.x) ? allowedWidth / textureSize.x : 1.0) + ; + + double angle = (startingAngle + itemAngle(nPlaces, place)) % 360; + vector2 xy = (sin(angle), -cos(angle)) * radius + center; + int width = int(textureSize.x * scale); + int height = int(textureSize.y * scale); + + drawIcon(texture, xy, width, height, angle, isTall); + drawAmmo(angle, center, viewModel, iconIndex); + } + + private + void drawIcon(TextureID texture, vector2 xy, int w, int h, double angle, bool isTall) const + { + bool flipX; + double scaleY; + + if (mIsRotating) + { + flipX = (angle > 180); + if (flipX) angle -= 180; + angle = -angle + 90; + + if (isTall) angle += flipX ? 90 : -90; + + scaleY = 1.0; + } + else + { + flipX = false; + angle = 0; + scaleY = mOptions.isPreservingAspectRatio() ? 1.2 : 1.0; + } + + if (!texture.isValid()) texture = mTextureCache.noIcon; + + Screen.drawTexture( texture + , NO_ANIMATION + , xy.x + , xy.y + , DTA_CenterOffset , true + , DTA_DestWidth , w + , DTA_DestHeight , h + , DTA_ScaleY , scaleY + , DTA_Alpha , mAlpha + , DTA_Rotate , angle + , DTA_FlipX , flipX + , DTA_KeepRatio, false + ); + + return; //no tint for you + //if (!mOptions.getWheelTint()) return; + + Screen.drawTexture( texture + , NO_ANIMATION + , xy.x + , xy.y + , DTA_CenterOffset , true + , DTA_DestWidth , w + , DTA_DestHeight , h + , DTA_ScaleY , scaleY + , DTA_Alpha , mAlpha * 0.3 + , DTA_FillColor , mBaseColor + , DTA_Rotate , angle + , DTA_FlipX , flipX + ); + + } + + private + void drawAmmoPip(double angle, double radius, vector2 center, bool colored) + { + vector2 xy = (sin(angle), -cos(angle)) * radius + center; + vector2 size = mTextureCache.ammoPipSize * mScaleFactor; + + if (colored) + { + Screen.drawTexture( mTextureCache.ammoPip + , NO_ANIMATION + , round(xy.x) + , round(xy.y) + , DTA_CenterOffset , true + , DTA_Alpha , mAlpha + , DTA_DestWidth , int(size.x) + , DTA_DestHeight , int(size.y) + , DTA_FillColor , FILLED_QUANTITY_COLOR + ); + } + else + { + Screen.drawTexture( mTextureCache.ammoPip + , NO_ANIMATION + , round(xy.x) + , round(xy.y) + , DTA_CenterOffset , true + , DTA_Alpha , mAlpha + , DTA_DestWidth , int(size.x) + , DTA_DestHeight , int(size.y) + ); + } + } + + private static + int, int makePipsNumbers(int items, int maxItems) + { + if (maxItems <= MAX_N_PIPS) return items, maxItems; + + int nColoredPips = int(ceil(MAX_N_PIPS * double(items) / maxItems)); + return nColoredPips, MAX_N_PIPS; + } + + private + void drawAmmo(double weaponAngle, vector2 center, gb_ViewModel viewModel, uint weaponIndex) + { + if (gb_Ammo.isValid(viewModel.quantity1[weaponIndex], viewModel.maxQuantity1[weaponIndex])) + { + int margin = int(10 * mScaleFactor); + int radius = mScreen.getScaledScreenHeight() / 4 - margin; + int nColoredPips, nTotalPips; + [nColoredPips, nTotalPips] = makePipsNumbers( viewModel.quantity1 [weaponIndex] + , viewModel.maxQuantity1[weaponIndex] + ); + drawQuantityPips(radius, weaponAngle, center, nColoredPips, nTotalPips); + } + + if (gb_Ammo.isValid(viewModel.quantity2[weaponIndex], viewModel.maxQuantity2[weaponIndex])) + { + int margin = int(20 * mScaleFactor); + int radius = mScreen.getScaledScreenHeight() / 4 - margin; + int nColoredPips, nTotalPips; + [nColoredPips, nTotalPips] = makePipsNumbers( viewModel.quantity2 [weaponIndex] + , viewModel.maxQuantity2[weaponIndex] + ); + drawQuantityPips(radius, weaponAngle, center, nColoredPips, nTotalPips); + } + } + + void drawQuantityPips( double radius + , double centerAngle + , vector2 center + , int nColoredPips + , int nTotalPips + ) + { + if (nTotalPips % 2 == 0) + { + int nTotalPipsHalved = nTotalPips / 2; + + for (int i = -nTotalPipsHalved + 1; i <= 0; ++i) + { + double angle = centerAngle - PIPS_GAP + i * PIPS_STEP; + drawAmmoPip(angle, radius, center, nColoredPips > 0); + --nColoredPips; + } + + for (int i = 0; i < nTotalPipsHalved; ++i) + { + double angle = centerAngle + PIPS_GAP + i * PIPS_STEP; + drawAmmoPip(angle, radius, center, nColoredPips > 0); + --nColoredPips; + } + } + else + { + double angleStart = centerAngle - (nTotalPips - 1) * PIPS_STEP / 2; + for (int i = 0; i < nTotalPips; ++i) + { + double angle = angleStart + i * PIPS_STEP; + drawAmmoPip(angle, radius, center, nColoredPips > 0); + --nColoredPips; + } + } + } + + private + void displaySlot(uint place, int slot, uint nPlaces, int radius) + { + double angle = itemAngle(nPlaces, place); + vector2 pos = (sin(angle), -cos(angle)) * radius + mCenter; + Font aFont = mFontSelector.getFont(); + + gb_Text.draw(string.format("%d", slot), pos, aFont, mAlpha, true,font.CR_SAPPHIRE); + } + + private static + double itemAngle(uint nItems, uint index) + { + return 360.0 / nItems * index; + } + + private + void drawHands(uint nPlaces, uint selectedIndex, vector2 center, double startAngle) + { + if (nPlaces < 2) return; + + double handsAngle = startAngle - itemAngle(nPlaces, selectedIndex); + double sectorAngleHalfWidth = max(6, 360.0 / 2.0 / nPlaces - 2); + double scaleFactor = mScreen.getScaleFactor(); + vector2 size = TexMan.getScaledSize(mTextureCache.hand) * scaleFactor; + + Screen.drawTexture( mTextureCache.hand + , NO_ANIMATION + , center.x + , center.y + , DTA_KeepRatio , true + , DTA_CenterOffset , true + , DTA_Alpha , mAlpha + , DTA_Rotate , handsAngle - sectorAngleHalfWidth + , DTA_FlipX , true + , DTA_DestWidthF , size.x + , DTA_DestHeightF , size.y + ); + + Screen.drawTexture( mTextureCache.hand + , NO_ANIMATION + , center.x + , center.y + , DTA_KeepRatio , true + , DTA_CenterOffset , true + , DTA_CenterOffset , true + , DTA_Alpha , mAlpha + , DTA_Rotate , handsAngle + sectorAngleHalfWidth + , DTA_DestWidthF , size.x + , DTA_DestHeightF , size.y + ); + } + + private + void drawWeaponDescription( gb_ViewModel viewModel + , int innerIndex + , int nPlaces + , bool isSlotExpanded + ) + { + int index = viewModel.selectedIndex; + string description = viewModel.tags[index]; + if (description.length() == 0) return; + + string ammo1 = gb_Ammo.isValid(viewModel.quantity1[index], viewModel.maxQuantity1[index]) + ? string.format("%d/%d", viewModel.quantity1[index], viewModel.maxQuantity1[index]) + : ""; + string ammo2 = gb_Ammo.isValid(viewModel.quantity2[index], viewModel.maxQuantity2[index]) + ? string.format("%d/%d", viewModel.quantity2[index], viewModel.maxQuantity2[index]) + : ""; + + double angle = itemAngle(nPlaces, innerIndex); + bool isOnTop = isSlotExpanded && (90.0 < angle && angle < 270.0); + vector2 pos = mCenter; + pos.y += mScreen.getWheelRadius() * (isOnTop ? -1 : 1); + + mText.drawBox(ammo1, description, ammo2, pos, !isOnTop, mBaseColor, mAlpha,font.CR_LIGHTBLUE,true); + } + + private + void drawPointer(double angle, double radius) + { + vector2 pos = (sin(angle), -cos(angle)) * radius + mCenter; + vector2 size = TexMan.getScaledSize(mTextureCache.pointer); + size *= mScaleFactor; + + Screen.drawTexture( mTextureCache.pointer + , NO_ANIMATION + , pos.x + , pos.y + , DTA_CenterOffset , true + , DTA_Alpha , mAlpha + , DTA_DestWidth , int(size.x) + , DTA_DestHeight , int(size.y) + , DTA_TranslationIndex, Translation.GetID('reddenwep') + ); + } + + const NO_ANIMATION = 0; // == false + + const MARGIN = 4; + + const UNDEFINED_INDEX = -1; + + const MAX_N_PIPS = 10; + + const PIPS_GAP = 1.2; + const PIPS_STEP = 1.5; + + const FILLED_QUANTITY_COLOR = 0x22DD22; + + private double mAlpha; + private color mBaseColor; + private vector2 mCenter; + + private gb_Screen mScreen; + private gb_Options mOptions; + private gb_FontSelector mFontSelector; + private gb_MultiWheelMode mMultiWheelMode; + + private gb_Text mText; + + private bool mIsRotating; + +// cache /////////////////////////////////////////////////////////////////////////////////////////// + + private gb_TextureCache mTextureCache; + private double mScaleFactor; + +} // class gb_WheelView diff --git a/zscript/m_gizmos/previous_weapon.zs b/zscript/m_gizmos/previous_weapon.zs new file mode 100644 index 0000000000..1f7a7780bc --- /dev/null +++ b/zscript/m_gizmos/previous_weapon.zs @@ -0,0 +1,119 @@ +/* Copyright Alexander 'm8f' Kromm (mmaulwurff@gmail.com) 2021 + * + * This file is a part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +/** + * To save data into savegame. + */ +class gb_PreviousWeaponStorage : EventHandler +{ + + Weapon mCurrentWeapon; + Weapon mPreviousWeapon; + bool mIsHolstered; + +} // class gb_PreviousWeaponEventHandlerStorage + +/** + * Main event handler is static so data is carried over to next level. + */ +class gb_PreviousWeaponEventHandler : StaticEventHandler +{ + +// public: // EventHandler ///////////////////////////////////////////////////////////////////////// + + override + void playerEntered(PlayerEvent event) + { + mStorage = gb_PreviousWeaponStorage(EventHandler.find("gb_PreviousWeaponStorage")); + if (event.playerNumber == consolePlayer) updatePreviousWeapon(); + } + + override + void worldLoaded(WorldEvent event) + { + if (!event.isSaveGame) return; + + mStorage = gb_PreviousWeaponStorage(EventHandler.find("gb_PreviousWeaponStorage")); + + if (mStorage == NULL) return; + + mCurrentWeapon = mStorage.mCurrentWeapon; + mPreviousWeapon = mStorage.mPreviousWeapon; + mIsHolstered = mStorage.mIsHolstered; + } + + override + void worldTick() + { + if (mStorage == NULL) return; + + updatePreviousWeapon(); + + mStorage.mCurrentWeapon = mCurrentWeapon; + mStorage.mPreviousWeapon = mPreviousWeapon; + mStorage.mIsHolstered = mIsHolstered; + } + + override + void networkProcess(ConsoleEvent event) + { + if (mStorage == NULL) return; + + if (event.name == "gb_prev_weapon" + && mPreviousWeapon != NULL + && mPreviousWeapon != players[event.player].ReadyWeapon) + { + players[event.player].PendingWeapon = mPreviousWeapon; + } + } + +// private: //////////////////////////////////////////////////////////////////////////////////////// + + private + void updatePreviousWeapon() + { + Weapon ready = players[consolePlayer].readyWeapon; + + if (ready == NULL || ready.getClassName() == "mg_Holstered") + { + if (!mIsHolstered) + { + // swap + let tmp = mCurrentWeapon; + mCurrentWeapon = mPreviousWeapon; + mPreviousWeapon = tmp; + } + mIsHolstered = true; + return; + } + + mIsHolstered = false; + + if (mCurrentWeapon != ready) + { + mPreviousWeapon = mCurrentWeapon; + } + + mCurrentWeapon = ready; + } + + private gb_PreviousWeaponStorage mStorage; + private Weapon mCurrentWeapon; + private Weapon mPreviousWeapon; + private bool mIsHolstered; + +} // class gb_PreviousWeaponEventHandler diff --git a/zscript/zabor/event_handler.zs b/zscript/zabor/event_handler.zs new file mode 100644 index 0000000000..0112c72096 --- /dev/null +++ b/zscript/zabor/event_handler.zs @@ -0,0 +1,326 @@ +/* Copyright Alexander Kromm (mmaulwurff@gmail.com) 2021 + * + * This file is part of Gearbox. + * + * Gearbox is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Gearbox 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 + * Gearbox. If not, see . + */ + +class gb_VmAbortHandler : EventHandler +{ + + override + void playerSpawned(PlayerEvent event) + { + if (event.playerNumber != consolePlayer) return; + + mPlayerClassName = players[consolePlayer].mo.getClassName(); + mSkillName = g_SkillName(); + } + + override + void uiTick() + { + if (level.totalTime % 35 == 0) rememberSystemTime(SystemTime.now()); + } + + override + void onDestroy() + { + if (gameState != GS_FullConsole + || !Cvar.getCvar("gb_zabor_enabled", players[consolePlayer]).getBool()) + { + return; + } + + + if (!amIFirst()) + { + printGearboxCvars(); + return; + } + + printInfo(); + printAttention(); + } + + override + void consoleProcess(ConsoleEvent event) + { + if (event.name != "zabor") return; + + if (!amIFirst()) + { + printGearboxCvars(); + return; + } + + printInfo(); + } + +// private: //////////////////////////////////////////////////////////////////////////////////////// + + private clearscope + void printInfo() + { + printZabor(getGameInfo(), getConfiguration(), getRealTime()); + printGearboxCvars(); + printEventHandlers(); + } + + private static clearscope + void printGearboxCvars() + { + Array gearboxConfiguration = + { + intCvarToString ("gb_scale" ), + colorCvarToString ("gb_color" ), + colorCvarToString ("gb_dim_color" ), + intCvarToString ("gb_show_tags" ), + + intCvarToString ("gb_view_type" ), + + intCvarToString ("gb_enable_dim" ), + intCvarToString ("gb_enable_blur" ), + floatCvarToString ("gb_wheel_position" ), + floatCvarToString ("gb_wheel_scale" ), + intCvarToString ("gb_wheel_tint" ), + intCvarToString ("gb_multiwheel_limit" ), + + floatCvarToString ("gb_blocks_position_x" ), + floatCvarToString ("gb_blocks_position_y" ), + + intCvarToString ("gb_text_scale" ), + floatCvarToString ("gb_text_position_x" ), + floatCvarToString ("gb_text_position_y" ), + floatCvarToString ("gb_text_position_y_max" ), + intCvarToString ("gb_text_usual_color" ), + intCvarToString ("gb_text_selected_color" ), + + stringCvarToString("gb_font" ), + + intCvarToString ("gb_open_on_scroll" ), + intCvarToString ("gb_open_on_slot" ), + intCvarToString ("gb_reverse_slot_cycle_order"), + intCvarToString ("gb_select_first_slot_weapon"), + intCvarToString ("gb_mouse_in_wheel" ), + intCvarToString ("gb_select_on_key_up" ), + intCvarToString ("gb_no_menu_if_one" ), + intCvarToString ("gb_on_automap" ), + intCvarToString ("gb_lock_positions" ), + intCvarToString ("gb_enable_sounds" ), + intCvarToString ("gb_frozen_can_open" ), + + intCvarToString ("gb_time_freeze" ), + + floatCvarToString ("gb_mouse_sensitivity_x" ), + floatCvarToString ("gb_mouse_sensitivity_y" ) + }; + + // cut prefix: + uint nCvars = gearboxConfiguration.size(); + for (uint i = 0; i < nCvars; ++i) + { + string value = gearboxConfiguration[i]; + if (value.left(3) == "gb_") + { + gearboxConfiguration[i] = value.mid(3); + } + } + + string report = "\cigb_*\c-: " .. join(gearboxConfiguration, ", "); + + Console.printf("%s", report); + } + + private static clearscope + string intCvarToString(string cvarName) + { + let aCvar = Cvar.getCvar(cvarName, players[consolePlayer]); + return aCvar ? string.format("%s:\cu%d\c-", cvarName, aCvar.getInt()) : ""; + } + + private static clearscope + string floatCvarToString(string cvarName) + { + let aCvar = Cvar.getCvar(cvarName, players[consolePlayer]); + return aCvar ? string.format("%s:\cu%f\c-", cvarName, aCvar.getFloat()) : ""; + } + + private static clearscope + string colorCvarToString(string cvarName) + { + let aCvar = Cvar.getCvar(cvarName, players[consolePlayer]); + return aCvar ? string.format("%s:\cu0x%x\c-", cvarName, aCvar.getInt()) : ""; + } + + private static clearscope + string stringCvarToString(string cvarName) + { + let aCvar = Cvar.getCvar(cvarName, players[consolePlayer]); + return aCvar ? string.format("%s:\cu%s\c-", cvarName, aCvar.getString()) : ""; + } + + private static clearscope + string getConfiguration() + { + Array configuration = + { + intCvarToString("compatflags"), + intCvarToString("compatflags2"), + intCvarToString("dmflags"), + intCvarToString("dmflags2"), + floatCvarToString("autoaim") + }; + + return string.format("%s", join(configuration, ", ")); + } + + private clearscope + void printAttention() + { + string userName = players[consolePlayer].getUserName(); + string message1 = string.format( " # %s\cg, please report this VM abort to mod author." + , userName + ); + string message2 = " # Attach screenshot to the report."; + string message3 = " # Type \"screenshot\" below to take a screenshot."; + + Array tokens; + userName.split(tokens, "\c"); + int colorCharsCount = (tokens.size() - 1) * 3; + int length = max(max(message1.length() - colorCharsCount, message2.length()), message3.length()); + + message1 = fillBox(message1, length); + message2 = fillBox(message2, length); + message3 = fillBox(message3, length); + + string hashes; + for (int i = 0; i < length; ++i) + { + hashes = hashes .. "#"; + } + Console.printf("\n\cg %s\n%s\n%s\n%s\n %s\n", hashes, message1, message2, message3, hashes); + } + + private static clearscope + string fillBox(string result, int length) + { + for (int i = result.length(); i < length; ++i) result.appendFormat(" "); + result.appendFormat(" #"); + return result; + } + + private static clearscope + void printZabor(string s1 = "", string s2 = "", string s3 = "") + { + Console.printf( + "\ci __ __ __ __ __ __\n" + "\ci/ \\/ \\/ \\/ \\/ \\/ \\\n" + "\ci|Za||bo||r ||v1||.1||.0| \c-%s\n" + "\ci|..||..||..||..||..||..| \c-%s\n" + "\ci|__||__||__||__||__||__| \c-%s\n" + , s1, s2, s3 + ); + } + + private clearscope + bool amIFirst() + { + uint nClasses = AllClasses.size(); + for (uint i = 0; i < nClasses; ++i) + { + class aClass = AllClasses[i]; + string className = aClass.getClassName(); + bool isVmAbortHandler = (className.indexOf("VmAbortHandler") != -1); + if (!isVmAbortHandler) continue; + + return aClass.getClassName() == getClassName(); + } + return false; + } + + private clearscope + string getGameInfo() + { + return string.format( "map:\cu%s\c-, time:\cu%d\c-, multiplayer:\cu%d\c-, " + "class:\cu%s\c-, skill:\cu%s\c-" + , level.mapName + , level.totalTime + , multiplayer + , mPlayerClassName + , mSkillName + ); + } + + private static clearscope + void printEventHandlers() + { + Array eventHandlers; + + uint nClasses = AllClasses.size(); + for (uint i = 0; i < nClasses; ++i) + { + class aClass = AllClasses[i]; + + if ( aClass is "StaticEventHandler" + && aClass != "StaticEventHandler" + && aClass != "EventHandler" + ) + { + eventHandlers.push(aClass.getClassName()); + } + } + + Console.printf("\cievent handlers\c-: %s", join(eventHandlers, ", ")); + } + + private clearscope + string getRealTime() + { + return string.format("system time: \cu%s", SystemTime.format("%F %T %Z", mSystemTime)); + } + + private static clearscope + string join(Array strings, string delimiter) + { + string result; + + uint nStrings = strings.size(); + for (uint i = 0; i < nStrings; ++i) + { + if (strings[i].length() == 0) continue; + + if (result.length() == 0) + { + result = strings[i]; + } + else + { + result.appendFormat("%s%s", delimiter, strings[i]); + } + } + + return result; + } + + private play + void rememberSystemTime(int value) const + { + mSystemTime = value; + } + + private string mPlayerClassName; + private string mSkillName; + private int mSystemTime; + +} // class gb_VmAbortHandler