From f508b7d4948ea50586ae951cf51d87f2aee4540e Mon Sep 17 00:00:00 2001 From: nathan Date: Fri, 15 May 2026 15:19:54 -0700 Subject: [PATCH] Implement crouch and prone stances --- AGRARIAN_DEVELOPMENT_ROADMAP.md | 4 +- Content/Input/Actions/IA_Crouch.uasset | Bin 0 -> 1160 bytes Content/Input/Actions/IA_Prone.uasset | Bin 0 -> 1155 bytes Content/Input/IMC_Default.uasset | Bin 10254 -> 11831 bytes .../Blueprints/BP_ThirdPersonCharacter.uasset | Bin 51752 -> 52121 bytes Docs/MovementAndTimeScaleBaseline.md | 20 ++++ Scripts/setup_stance_input.py | 101 +++++++++++++++++ Scripts/verify_stance_input.py | 63 +++++++++++ Source/AgrarianGame/AgrarianGameCharacter.cpp | 104 +++++++++++++++++- Source/AgrarianGame/AgrarianGameCharacter.h | 40 +++++++ 10 files changed, 329 insertions(+), 3 deletions(-) create mode 100644 Content/Input/Actions/IA_Crouch.uasset create mode 100644 Content/Input/Actions/IA_Prone.uasset create mode 100644 Scripts/setup_stance_input.py create mode 100644 Scripts/verify_stance_input.py diff --git a/AGRARIAN_DEVELOPMENT_ROADMAP.md b/AGRARIAN_DEVELOPMENT_ROADMAP.md index 32edd7a..d0e06f0 100644 --- a/AGRARIAN_DEVELOPMENT_ROADMAP.md +++ b/AGRARIAN_DEVELOPMENT_ROADMAP.md @@ -375,7 +375,7 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe - [x] Define real-world baseline walking speed. Decision: baseline adult walking speed is `1.4 m/s` (`140 Unreal units/s`), with MVP tuning allowance up to about `1.6 m/s` for brisk walking. Movement speed does not scale with the `4 real hours = 1 in-game day` calendar. - [x] Define real-world baseline running speed. Decision: sustainable adult running target is `3.0 m/s` (`300 Unreal units/s`); MVP sprint is a short burst at `5.5 m/s` (`550 Unreal units/s`) with stamina limits. Movement speed does not scale with the calendar. - [x] Connect movement speed to age, condition, strength, endurance, hunger, thirst, injury, carried weight, and terrain. Implemented with live survival/inventory modifiers plus replicated age, condition, strength, endurance, and terrain hooks. -- [ ] Implement crouching if needed. +- [x] Implement crouching and prone movement stances. Decision: `C` toggles crouch at `55%` movement speed and `Z` toggles prone at `25%` movement speed. Gamepad mappings are Right Shoulder for crouch and Left Shoulder for prone. Sprint is disabled while crouched or prone. - [x] Implement jumping if needed. - [x] Implement interaction trace. - [ ] Implement interact prompt. @@ -1465,4 +1465,4 @@ Earliest incomplete foundation items: Immediate next item: -- [ ] Implement crouching if needed. +- [ ] Implement interact prompt. diff --git a/Content/Input/Actions/IA_Crouch.uasset b/Content/Input/Actions/IA_Crouch.uasset new file mode 100644 index 0000000000000000000000000000000000000000..641ce6cac86ff86b1b7d89adcab12dd8d8236efe GIT binary patch literal 1160 zcmX@utTpfZ|Ns9Jm>C$jm>3v-0cjBU^YD-7%z3A?Zmf2=|NfJ~V;fDcbaxg821cOV z+l&oQUOG+r!hOqdk>hSRJq@6k91!cfC+4Q=d*&6CmgqYsmt^MW73+IC#yc0~mnLTb zka-{sauh2A zI|B=Y+W|plAs|m%JJi)wJ18|LHL*BV*Hq6OEYHC3KISSDc6p#CFv$3je;>r?0NNV@ z3@&5<4CVizAOtx*0!fY)$oI?7OJ(@Ne3Vxf$O%YH&Q45E^~q08%*m`u1O=oYFl37h z5|dLIgi0zq2nV*J}ee5cmfSGIj_Pg@maCSqcM4Aqo>>00lXm17v_a1_Inb z%nN2QFo2>27l0)ZkUY9s$a+9Y2p0gEfgWI}K^g}1jycp7Al0a{N81fU0&RVQesI@lp$i8-aIA(aKG44}Yv%FhR8iac-{hD8EU m7!@1@mMPP}Ep5=VK5`M{E*Vs5hO&DWfh?!?YZ^esLFxhUoa+hz literal 0 HcmV?d00001 diff --git a/Content/Input/Actions/IA_Prone.uasset b/Content/Input/Actions/IA_Prone.uasset new file mode 100644 index 0000000000000000000000000000000000000000..974126180ce1cd80e34b1106d00fc23de2801da9 GIT binary patch literal 1155 zcmX@utTpfZ|Ns9Jm>C$jm>3v-0cjBE6Sw$qXr1Uhb)Iu4>m@hLj`sXl@5I8uzzCFk zo3Y`^OQ$JcxNjLQa@_5vrvVg`1!8^o#N1SU&%A=t5`D+ylFaz{1VY$iUFaz{J4C)z}rT7^Lqu zP!kX_bb|;W@P@h;M1u^3@%@2(5RQk+gVb8w=c|0b^p)@43FpPi*@S+8%mZPNqgWZ( z8CV$H4hS*}0eRZmp{}OdL8&>ZiN&e9rh4XJc?O2}F;|(e%L6rm!CNJ6br7QkWIr&p zkO44||ARsh0}q zU;sr3E&xj)AbE7NkoAB<2p0gEfgWHeAsP(yjSN8*w+KgsuMI*oq-EzKv-fU34 zQN(B(chz9k<+aHDFu>6qMSnigwTMzIUguV?QQw z+Uzu(M8)gu(2)#CW)rOvx-g)6SH|#iZkI5 zp{uy4*jZjsSWxC(>~gzZ?jo0vhVWHf>X}M3i=S;=2bE0kdqPIm{mjR;#cGGtzwWD_ zmA`vm^?`$bqsM-{ZX4bMI#=*Di85#+RUVo?fk#X^Icwd8IcwCQszlV>!n|V9PIUgB zqvHo|=T3qO+n;(Ag_qdwgglcc?~Hjb0L^7ahOG_bad^CXAZ$3?A-@vxs!akuI@6r;^k6()hn^c`Hyj6eU zmkEt^{%A{M#0YqsDO1VnyvEkNCgAf!=f>V+?T89#O7P=KC)FK1VSiE4YSa44gM}S8 za&HkWmit^<$WXT!R;@xY6xtO$d0--oG3IpOj=OhbMsc}@0iydo;fe6I7`Wq+wzE^L zipL*dD7H;;I^w_o`kaE>rfw{xiQ~_G-o3toYPbnpX)1wkim2=!%5daWun-wZWT%MNfp@*naV? z$t)qjp{d>3L4>P^K6-5R1Asgs%xLM}ce*en5A>HSI+ech{1LpK$Izp;*UP8pFF~$O z5BPi-=*eX*f5Hm7HcZYJ*YAXtb@b|IjZdJop2mNlaT=w1>fC-j|JPiyua9PMMjTaT z4K7`adi^vvvtxbM;8IoWMqTk5^!i`+%)DW6xs}nDFy*$r`}-keR%u!@{qXFX{2^qj zwN{nR|L(#ixMYwwXV+t2}I~o?|%w`f;B%Wvb+t;A7MAe zb`DBvTQ4$Ysn~j+;q9^9Dr5PQG?1K`6c7z3S79j`PR^aAT;%|A@;%F}!q+^Gk|gB5 zV!7Lrkh{QgrAf%0XSpRw$bHFjwb)-LU%dlayPOq+CIga*LCcb0#TgiS-*V!{00UcrLRloKe%v zIc5dv>0mUD8*qUVb(viAKHbvwqs<=rVb0=-B@>we$dHaUtNrfM7MMfXgFete_OSEX z?LC-6dp{(M4{J^0_#Pi&dq_O&<9lv|?IB;Z+k0t*?crc!xA$ra?OjUnANGJm|NS+E z_P$TBhrJ{b&-X^y9+Gc6o*#~|J)~fEd!LW6y~KFwPN6*>%FiOiR$ta-o~o^5nfMH0 z|5g&@&j+>ET8w^)x#B|R$+TA6~31_t3x>~su=I2=Dfm<%8Y**jx{0ws2S5}lC&FF-4kT|t)M zm^aBM9AZF{0gYK;SS0y`brqB)?&eK2_V6UenFM5iN6CXmT#~X;Kt2t^@ko+D=~k;I z)0bPUI@}Gy_BBnE`fCLXQ(2NwhOa)Lo6N^g-yM8uDQzLcksKrkl7^58Zy`vqG7cJP z-6@T<9AL(j#;}P=ff<1#HYVdJK{t<0fzlYdc*J2p9wpe|DNq{2HvD#w+DVG+1(e2+ z#Ul-y_9#V~*kjWhw)onH0(SOMf(|v z@c4BPG^QcDn9?Z2$1Bif$V?o>eds8|S1!<)hO8^4QD*%Lbs4hcKS$z^U!XA^W#6SV z%J4x2bQv-e2XP;IEffD18S9XJlhP>rjRo$Qjs%T&I&VX{ix)wiGP1HTmBse^8W*xXdsgS literal 10254 zcmeHN3v82B6h1tMjQ2}kD#&w;Z7{kG#&hdBEU@t?uM7?RD?eSOYwO>?g8>RcBm^M@ zgQy52AxdIM2m%HIF+dbW2?=Nj2?!c6h=9C7-t~Nc?_Yk(Hk!~imT;24|Gnp)d(OSz zIrrXk+wNP`tK#24ATX^CQN!9qcliw(w0?Kn-fi9~y0me9liP*e8(!T1co^?G76B0L@V(dn0YMQ!@5+ElQ8_fNF^VXL_q z2XqeOryeCxUCQ6vybiP{Cr_|sB#)C_vgDIfGE%d&cB0E~9<5z@J9pA3qvGSGD2!yg z$9n{M`X^%^0H8_CIAv&S!}Tf}La@*zU8hmtg^TCU-r?+ZcvqzC>e80f2 za7+&EEzSD_VC2|Ur^ii~6D__bs49w7K1T6)WkoF~>y|Ul#>~wvSLG1%Y+=e?=*Xi@ z^~;*IL@DU1k#c!~w-k|kI9cgvN zKocxl-2T###j(fM4eJ8t7?)HoD=;DzyJYTWlU0@N<;o>enbV{A$l>p{Zb(F;+1XZi ziL5wPp~IK2cuEBt%8t<;+Ormfq*t0gHD9vJxqemkxTofN_*F{VdL1z#YAcZ#CC^Z) zsQ<(Zh%vn#%=A3!K5gFWm&4fOOZ=t9KGkWTN$sRgM-X{>uW?RC3A#-S9IG%|)A)vd zTi?&v{2KSu94&lc_PAAfwkm3Qk}APOsA&>IdC6{xKgt&w*pO?|67HattxTLBO z@X@Y!uYXscB{Xm-@m7)xF=wMw%g4+Is9~`}LpJQcfhpObKUY#Hd)L~-7@kd4{D$H6 zp|$rRH(qf%9GK|oVWnST^PAu$(-)H#!pcPY=4|l_lqON_dkN<{aml!$1>?$>X3d#u zWw$7jU8SAhu4&z|YPk{qQZJ<}Iq+dMGNU}6ne^b(aeb=H$yBa6;_UEQ`l`gdzeE9eqC6te`&|Y+86!)V29taCd5lXk>XYm*mq6Z16gB z9vk=dt>wv@HNz3J>}vKljfcZWYymCWPHTkmSF)t&R{x=Au=@tv!~)2ZrRhb8@-7%9gzcMgsu};W0 zd`WAr5)LFd7ZlOS*e57UeZ~6{pD@{?j7ZX{GP!}G@o92{B9t2(p&T}KkkM|CLo`4X zZty(PMY%e;>nztV0=a7}XNo}XD$8X=Aa{l3G9!??%yL6GClRS^~t9#FWhAUF7F5xO24?4cjl z1l~9$Z~}rx8ZxyY=h3Hb3!LigK_93xdpK*1_9jQs-h;6DVJ(NxPp)BmIAD$QQ)<{A z@*<*~4BPj_1Z2wudCs=)Y|>Y!CU1(caz~ zwwDlQ?@$!&vFc|c#MYy%CU^xD*AMNcY`o4l<)rKyKOpoA&&6;~={ci*OVl!=)XFk3 zT|*(?7RuycK4~#%Fl3#nT5FGpb@$M|FbD*&5eb41K**6n`n>hB#uOikI4h9Ofv^}r z=wR%OlobTNk$ym4hZ}r1!8IztRY@nEPq-R^bOtn5DyUEAiN-s;h_!T*dm6{6^Ptf< z&LpmrI1s4=DuR%rQWsIt)3Db?9Y-<<#}`}=GHOK~OIasPnSQ3?iVJ#T>|pAa;ubP| z{Q}Y_5{Hm!-hw-^GgcXC_!d!*s%MY1p1_PLj$zXl0#*d}1FYZ}LD!j0f#MjtP{iRp zjuC9|6ex~i(~pT*K?d)D;ux|}q~QpQQKWU(K#ObGLVFtuIN4(ao#rk!7snA7>qXj$ z;R3}mWT8chR6RzKhTn5QaSU5%k>Y3rhB1Op+ymnox=_RcvMOP_9o3WOm63&?^vXrI+z;l*Dho^Qx*Mm5Ux@8q8AD2py+`2Ps#xrXdkRHF<(yMtap z7Q{jOtbQ-kfBcI*WV%KDtqu17RoTb??EB=_mkUw{et!fB;A5lXdfBP|W9$CBCrSVL H8S?)D52Lz& diff --git a/Content/ThirdPerson/Blueprints/BP_ThirdPersonCharacter.uasset b/Content/ThirdPerson/Blueprints/BP_ThirdPersonCharacter.uasset index 4f1c10ca46f898793a8a61ab202b4f64c2b0d389..337768a150715a3969c4eb5e76faf0860325c3a0 100644 GIT binary patch delta 9596 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 delta 8994 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 {key_name}") + return + + key = unreal.Key() + key.set_editor_property("key_name", key_name) + + mapping_data = context.get_editor_property("default_key_mappings") + mappings = list(mapping_data.get_editor_property("mappings")) + new_mapping = unreal.EnhancedActionKeyMapping() + new_mapping.set_editor_property("action", action) + new_mapping.set_editor_property("key", key) + mappings.append(new_mapping) + mapping_data.set_editor_property("mappings", mappings) + context.set_editor_property("default_key_mappings", mapping_data) + + unreal.log(f"Added mapping: {action.get_name()} -> {key_name}") + + +def main(): + crouch_action = create_input_action("/Game/Input/Actions/IA_Crouch") + prone_action = create_input_action("/Game/Input/Actions/IA_Prone") + set_boolean_value_type(crouch_action) + set_boolean_value_type(prone_action) + + context = load("/Game/Input/IMC_Default") + map_key(context, crouch_action, "C") + map_key(context, crouch_action, "Gamepad_RightShoulder") + map_key(context, prone_action, "Z") + map_key(context, prone_action, "Gamepad_LeftShoulder") + unreal.EditorAssetLibrary.save_loaded_asset(context) + + character_bp = load("/Game/ThirdPerson/Blueprints/BP_ThirdPersonCharacter") + character_cdo = unreal.get_default_object(character_bp.generated_class()) + character_cdo.set_editor_property("CrouchAction", crouch_action) + character_cdo.set_editor_property("ProneAction", prone_action) + for property_name, value in STANCE_DEFAULTS.items(): + character_cdo.set_editor_property(property_name, value) + unreal.EditorAssetLibrary.save_loaded_asset(character_bp) + + unreal.log("Agrarian stance input setup complete.") + + +main() diff --git a/Scripts/verify_stance_input.py b/Scripts/verify_stance_input.py new file mode 100644 index 0000000..a907a49 --- /dev/null +++ b/Scripts/verify_stance_input.py @@ -0,0 +1,63 @@ +import math +import unreal + + +STANCE_DEFAULTS = { + "CrouchSpeedMultiplier": 0.55, + "ProneSpeedMultiplier": 0.25, +} + + +def load(path): + asset = unreal.EditorAssetLibrary.load_asset(path) + if not asset: + raise RuntimeError(f"Could not load {path}") + return asset + + +def mapping_found(context, action, key_name): + mapping_data = context.get_editor_property("default_key_mappings") + for mapping in list(mapping_data.get_editor_property("mappings")): + mapping_key = mapping.get_editor_property("key") + if ( + mapping.get_editor_property("action") == action + and str(mapping_key.get_editor_property("key_name")) == key_name + ): + return True + return False + + +def main(): + crouch_action = load("/Game/Input/Actions/IA_Crouch") + prone_action = load("/Game/Input/Actions/IA_Prone") + context = load("/Game/Input/IMC_Default") + character_bp = load("/Game/ThirdPerson/Blueprints/BP_ThirdPersonCharacter") + character_cdo = unreal.get_default_object(character_bp.generated_class()) + + missing = [] + for action, key_name in [ + (crouch_action, "C"), + (crouch_action, "Gamepad_RightShoulder"), + (prone_action, "Z"), + (prone_action, "Gamepad_LeftShoulder"), + ]: + if not mapping_found(context, action, key_name): + missing.append(f"missing mapping {action.get_name()} -> {key_name}") + + if character_cdo.get_editor_property("CrouchAction") != crouch_action: + missing.append("BP_ThirdPersonCharacter CrouchAction is not IA_Crouch") + if character_cdo.get_editor_property("ProneAction") != prone_action: + missing.append("BP_ThirdPersonCharacter ProneAction is not IA_Prone") + + for property_name, expected in STANCE_DEFAULTS.items(): + actual = float(character_cdo.get_editor_property(property_name)) + if not math.isclose(actual, expected, rel_tol=0.0, abs_tol=0.01): + missing.append(f"{property_name}: expected {expected}, got {actual}") + + if missing: + raise RuntimeError("Stance input verification failed: " + "; ".join(missing)) + + unreal.log("Agrarian stance input verification complete.") + + +main() diff --git a/Source/AgrarianGame/AgrarianGameCharacter.cpp b/Source/AgrarianGame/AgrarianGameCharacter.cpp index 8f6eba1..613d56c 100644 --- a/Source/AgrarianGame/AgrarianGameCharacter.cpp +++ b/Source/AgrarianGame/AgrarianGameCharacter.cpp @@ -31,6 +31,7 @@ AAgrarianGameCharacter::AAgrarianGameCharacter() bUseControllerRotationRoll = false; // Configure character movement + GetCharacterMovement()->NavAgentProps.bCanCrouch = true; GetCharacterMovement()->bOrientRotationToMovement = true; GetCharacterMovement()->RotationRate = FRotator(0.0f, 500.0f, 0.0f); @@ -39,6 +40,7 @@ AAgrarianGameCharacter::AAgrarianGameCharacter() GetCharacterMovement()->JumpZVelocity = 500.f; GetCharacterMovement()->AirControl = 0.35f; GetCharacterMovement()->MaxWalkSpeed = WalkSpeed; + GetCharacterMovement()->MaxWalkSpeedCrouched = WalkSpeed * CrouchSpeedMultiplier; GetCharacterMovement()->MinAnalogWalkSpeed = 20.f; GetCharacterMovement()->BrakingDecelerationWalking = 2000.f; GetCharacterMovement()->BrakingDecelerationFalling = 1500.0f; @@ -95,6 +97,7 @@ void AAgrarianGameCharacter::GetLifetimeReplicatedProps(TArrayBindAction(SprintAction, ETriggerEvent::Canceled, this, &AAgrarianGameCharacter::StopSprint); } + if (CrouchAction) + { + EnhancedInputComponent->BindAction(CrouchAction, ETriggerEvent::Started, this, &AAgrarianGameCharacter::ToggleCrouchStance); + } + + if (ProneAction) + { + EnhancedInputComponent->BindAction(ProneAction, ETriggerEvent::Started, this, &AAgrarianGameCharacter::ToggleProneStance); + } + if (ToggleCameraAction) { EnhancedInputComponent->BindAction(ToggleCameraAction, ETriggerEvent::Started, this, &AAgrarianGameCharacter::ToggleCameraPerspective); @@ -166,6 +179,11 @@ void AAgrarianGameCharacter::Interact() void AAgrarianGameCharacter::StartSprint() { + if (bIsProne || bIsCrouched) + { + return; + } + SetWantsToSprint(true); } @@ -174,6 +192,32 @@ void AAgrarianGameCharacter::StopSprint() SetWantsToSprint(false); } +void AAgrarianGameCharacter::ToggleCrouchStance() +{ + if (bIsProne) + { + SetProne(false); + return; + } + + SetWantsToSprint(false); + if (bIsCrouched) + { + UnCrouch(); + } + else + { + Crouch(); + } + + ApplyMovementSpeed(); +} + +void AAgrarianGameCharacter::ToggleProneStance() +{ + SetProne(!bIsProne); +} + void AAgrarianGameCharacter::ToggleCameraPerspective() { SetFirstPersonCamera(!bFirstPersonCamera); @@ -235,6 +279,8 @@ bool AAgrarianGameCharacter::CanSprint() const { return SurvivalComponent && SurvivalComponent->IsAlive() + && !bIsProne + && !bIsCrouched && SurvivalComponent->Survival.Stamina > MinSprintStamina; } @@ -253,6 +299,29 @@ float AAgrarianGameCharacter::GetCurrentMovementSpeedMultiplier() const return CalculateMovementSpeedMultiplier(); } +void AAgrarianGameCharacter::SetProne(bool bNewProne) +{ + if (bIsProne == bNewProne) + { + return; + } + + bIsProne = bNewProne; + + if (bIsProne) + { + SetWantsToSprint(false); + UnCrouch(); + } + + ApplyMovementSpeed(); + + if (!HasAuthority()) + { + ServerSetProne(bIsProne); + } +} + void AAgrarianGameCharacter::SetTerrainMovementMultiplier(float NewTerrainMovementMultiplier) { TerrainMovementMultiplier = FMath::Clamp(NewTerrainMovementMultiplier, 0.25f, 1.25f); @@ -264,7 +333,9 @@ void AAgrarianGameCharacter::ApplyMovementSpeed() if (UCharacterMovementComponent* MovementComponent = GetCharacterMovement()) { const float BaseSpeed = IsSprinting() ? SprintSpeed : WalkSpeed; - MovementComponent->MaxWalkSpeed = FMath::Max(0.0f, BaseSpeed * CalculateMovementSpeedMultiplier()); + const float FinalSpeed = FMath::Max(0.0f, BaseSpeed * CalculateMovementSpeedMultiplier()); + MovementComponent->MaxWalkSpeed = FinalSpeed; + MovementComponent->MaxWalkSpeedCrouched = FinalSpeed; } } @@ -278,6 +349,7 @@ float AAgrarianGameCharacter::CalculateMovementSpeedMultiplier() const * TraitMultiplier * CalculateSurvivalMovementMultiplier() * CalculateCarryWeightMovementMultiplier() + * CalculateStanceMovementMultiplier() * FMath::Clamp(TerrainMovementMultiplier, 0.25f, 1.25f), 0.15f, 1.35f); @@ -353,11 +425,36 @@ float AAgrarianGameCharacter::CalculateCarryWeightMovementMultiplier() const CurrentCarryWeight); } +float AAgrarianGameCharacter::CalculateStanceMovementMultiplier() const +{ + if (bIsProne) + { + return FMath::Clamp(ProneSpeedMultiplier, 0.05f, 1.0f); + } + + if (bIsCrouched) + { + return FMath::Clamp(CrouchSpeedMultiplier, 0.1f, 1.0f); + } + + return 1.0f; +} + void AAgrarianGameCharacter::OnRep_SprintState() { ApplyMovementSpeed(); } +void AAgrarianGameCharacter::OnRep_ProneState() +{ + if (bIsProne) + { + UnCrouch(); + } + + ApplyMovementSpeed(); +} + void AAgrarianGameCharacter::DoMove(float Right, float Forward) { if (GetController() != nullptr) @@ -448,6 +545,11 @@ void AAgrarianGameCharacter::ServerSetWantsToSprint_Implementation(bool bNewWant SetWantsToSprint(bNewWantsToSprint); } +void AAgrarianGameCharacter::ServerSetProne_Implementation(bool bNewProne) +{ + SetProne(bNewProne); +} + void AAgrarianGameCharacter::ServerInteract_Implementation(AActor* TargetActor) { if (!TargetActor || !TargetActor->GetClass()->ImplementsInterface(UAgrarianInteractable::StaticClass())) diff --git a/Source/AgrarianGame/AgrarianGameCharacter.h b/Source/AgrarianGame/AgrarianGameCharacter.h index 7df63f0..758be0e 100644 --- a/Source/AgrarianGame/AgrarianGameCharacter.h +++ b/Source/AgrarianGame/AgrarianGameCharacter.h @@ -77,6 +77,14 @@ protected: UPROPERTY(EditAnywhere, Category="Input") UInputAction* SprintAction; + /** Toggle crouch movement stance. */ + UPROPERTY(EditAnywhere, Category="Input") + UInputAction* CrouchAction; + + /** Toggle prone movement stance. */ + UPROPERTY(EditAnywhere, Category="Input") + UInputAction* ProneAction; + /** Toggle between third-person and first-person camera views. */ UPROPERTY(EditAnywhere, Category="Input") UInputAction* ToggleCameraAction; @@ -101,6 +109,14 @@ protected: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Agrarian|Movement", meta = (ClampMin = "0", ClampMax = "100")) float MinSprintStamina = 5.0f; + /** Movement multiplier while crouched. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Agrarian|Movement|Stance", meta = (ClampMin = "0.1", ClampMax = "1")) + float CrouchSpeedMultiplier = 0.55f; + + /** Movement multiplier while prone. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Agrarian|Movement|Stance", meta = (ClampMin = "0.05", ClampMax = "1")) + float ProneSpeedMultiplier = 0.25f; + /** Age hook used by movement until full lifecycle/aging systems own it. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category="Agrarian|Movement|Modifiers", meta = (ClampMin = "0")) float AgeYears = 25.0f; @@ -145,6 +161,10 @@ protected: UPROPERTY(ReplicatedUsing = OnRep_SprintState, VisibleAnywhere, BlueprintReadOnly, Category="Agrarian|Movement", meta = (AllowPrivateAccess = "true")) bool bWantsToSprint = false; + /** Replicated prone stance for low crawl movement. */ + UPROPERTY(ReplicatedUsing = OnRep_ProneState, VisibleAnywhere, BlueprintReadOnly, Category="Agrarian|Movement|Stance", meta = (AllowPrivateAccess = "true")) + bool bIsProne = false; + public: /** Constructor */ @@ -175,6 +195,12 @@ protected: /** Called when sprint input is released. */ void StopSprint(); + /** Called when crouch input is pressed. */ + void ToggleCrouchStance(); + + /** Called when prone input is pressed. */ + void ToggleProneStance(); + /** Called for camera perspective toggle input */ void ToggleCameraPerspective(); @@ -196,10 +222,14 @@ protected: float CalculateAgeMovementMultiplier() const; float CalculateSurvivalMovementMultiplier() const; float CalculateCarryWeightMovementMultiplier() const; + float CalculateStanceMovementMultiplier() const; UFUNCTION() void OnRep_SprintState(); + UFUNCTION() + void OnRep_ProneState(); + public: /** Handles move inputs from either controls or UI interfaces */ @@ -230,6 +260,12 @@ public: UFUNCTION(BlueprintPure, Category="Agrarian|Movement") bool IsSprinting() const; + UFUNCTION(BlueprintPure, Category="Agrarian|Movement|Stance") + bool IsProne() const { return bIsProne; } + + UFUNCTION(BlueprintCallable, Category="Agrarian|Movement|Stance") + void SetProne(bool bNewProne); + UFUNCTION(BlueprintPure, Category="Agrarian|Movement") float GetCurrentCarryWeight() const; @@ -247,6 +283,10 @@ public: UFUNCTION(Server, Reliable) void ServerSetWantsToSprint(bool bNewWantsToSprint); + /** Server-authoritative prone stance update. */ + UFUNCTION(Server, Reliable) + void ServerSetProne(bool bNewProne); + public: /** Returns CameraBoom subobject **/