1 //**************************************************************************
3 //** ## ## ## ## ## #### #### ### ###
4 //** ## ## ## ## ## ## ## ## ## ## #### ####
5 //** ## ## ## ## ## ## ## ## ## ## ## ## ## ##
6 //** ## ## ######## ## ## ## ## ## ## ## ### ##
7 //** ### ## ## ### ## ## ## ## ## ##
8 //** # ## ## # #### #### ## ##
10 //** Copyright (C) 1999-2006 Jānis Legzdiņš
11 //** Copyright (C) 2018-2023 Ketmar Dark
13 //** This program is free software: you can redistribute it and/or modify
14 //** it under the terms of the GNU General Public License as published by
15 //** the Free Software Foundation, version 3 of the License ONLY.
17 //** This program is distributed in the hope that it will be useful,
18 //** but WITHOUT ANY WARRANTY; without even the implied warranty of
19 //** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 //** GNU General Public License for more details.
22 //** You should have received a copy of the GNU General Public License
23 //** along with this program. If not, see <http://www.gnu.org/licenses/>.
25 //**************************************************************************
26 #include "../gamedefs.h"
27 #include "../net/network.h" /* for demos and RPC */
28 #include "../server/server.h"
29 #include "../server/sv_local.h"
30 #include "../client/cl_local.h"
31 #include "../language.h"
32 #include "../infostr.h"
34 #include "p_levelinfo.h"
37 # include "../automap.h"
38 # include "../client/client.h"
39 # include "../sound/sound.h"
41 #include "../utils/qs_data.h"
43 //#define VV_SETSTATE_DEBUG
45 #ifdef VV_SETSTATE_DEBUG
46 # define VSLOGF(...) GCon->Logf(NAME_Debug, __VA_ARGS__)
48 # define VSLOGF(...) do {} while (0)
52 IMPLEMENT_CLASS(V
, BasePlayer
)
54 bool VBasePlayer::isCheckpointSpawn
= false;
56 static VCvarF
hud_notify_time("hud_notify_time", "3", "Notification timeout, in seconds.", CVAR_Archive
|CVAR_NoShadow
);
57 static VCvarF
center_msg_time("hud_center_message_time", "3", "Centered message timeout.", CVAR_Archive
|CVAR_NoShadow
);
58 static VCvarB
hud_msg_echo("hud_msg_echo", true, "Echo messages?", CVAR_Archive
|CVAR_NoShadow
);
59 static VCvarI
hud_font_color("hud_font_color", "11", "Font color.", CVAR_Archive
|CVAR_NoShadow
);
60 static VCvarI
hud_font_color_centered("hud_font_color_centered", "11", "Secondary font color.", CVAR_Archive
|CVAR_NoShadow
);
62 static VCvarF
hud_chat_time("hud_chat_time", "8", "Chat messages timeout, in seconds.", CVAR_Archive
|CVAR_NoShadow
);
63 static VCvarI
hud_chat_nick_color("hud_chat_nick_color", "8", "Chat nick color.", CVAR_Archive
|CVAR_NoShadow
);
64 static VCvarI
hud_chat_text_color("hud_chat_text_color", "13", "Chat font color.", CVAR_Archive
|CVAR_NoShadow
);
70 //VField *VBasePlayer::fldPendingWeapon = nullptr;
71 //VField *VBasePlayer::fldReadyWeapon = nullptr;
73 #define WEAPONBOTTOM (128.0f)
74 #define WEAPONTOP (32.0f)
77 const int VPSpriteRenderOrder
[NUMPSPRITES
] = {
85 struct SavedVObjectPtr
{
88 SavedVObjectPtr (VObject
**aptr
) : ptr(aptr
), saved(*aptr
) {}
89 ~SavedVObjectPtr() { *ptr
= saved
; }
93 struct SetViewStateGuard
{
98 VV_DISABLE_COPY(SetViewStateGuard
)
99 // constructor increases invocation count
100 inline SetViewStateGuard (VBasePlayer
*aplr
, int apos
) noexcept
: plr(aplr
), pos(apos
) { /*GCon->Logf(NAME_Debug, "VSG:CTOR: wc[%d]=%d", pos, plr->ViewStates[pos].WatchCat);*/ aplr
->ViewStates
[pos
].WatchCat
= 0; }
101 inline ~SetViewStateGuard () noexcept
{ /*GCon->Logf(NAME_Debug, "VSG:DTOR: wc[%d]=%d", pos, plr->ViewStates[pos].WatchCat);*/ plr
->ViewStates
[pos
].WatchCat
= 0; }
106 //==========================================================================
108 // VBasePlayer::PostCtor
110 //==========================================================================
111 void VBasePlayer::PostCtor () {
114 GCon->Logf("********** BASEPLAYER POSTCTOR (%s) **********", *GetClass()->GetFullName());
115 VField *field = GetClass()->FindField("k8ElvenGiftMessageTime");
116 if (field) GCon->Logf(" k8ElvenGiftMessageTime=%g", field->GetFloat(this));
122 //==========================================================================
124 // VBasePlayer::ExecuteNetMethod
126 //==========================================================================
127 bool VBasePlayer::ExecuteNetMethod (VMethod
*Func
) {
128 //if (onExecuteNetMethodCB) return onExecuteNetMethodCB(this, func);
130 if (GDemoRecordingContext
) {
131 // find initial version of the method
132 VMethod
*Base
= Func
;
133 while (Base
->SuperMethod
) Base
= Base
->SuperMethod
;
134 // execute it's replication condition method
135 vassert(Base
->ReplCond
);
136 vuint32 SavedFlags
= PlayerFlags
;
137 PlayerFlags
&= ~VBasePlayer::PF_IsClient
;
138 bool ShouldSend
= false;
139 if (VObject::ExecuteFunctionNoArgs(this, Base
->ReplCond
, true).getBool()) ShouldSend
= true; // no VMT lookups
140 PlayerFlags
= SavedFlags
;
143 // replication condition is true, the method must be replicated
144 GDemoRecordingContext
->ClientConnections
[0]->GetPlayerChannel()->SendRpc(Func
, this);
149 if (GGameInfo
->NetMode
== NM_TitleMap
||
150 GGameInfo
->NetMode
== NM_Standalone
||
151 (GGameInfo
->NetMode
== NM_ListenServer
&& this == cl
))
157 // find initial version of the method
158 VMethod
*Base
= Func
;
159 while (Base
->SuperMethod
) Base
= Base
->SuperMethod
;
160 // execute it's replication condition method
161 vassert(Base
->ReplCond
);
162 if (!VObject::ExecuteFunctionNoArgs(this, Base
->ReplCond
, true).getBool()) return false; // no VMT lookups
165 // replication condition is true, the method must be replicated
166 Net
->GetPlayerChannel()->SendRpc(Func
, this);
169 // clean up parameters
170 Func
->CleanupParams();
172 // it's been handled here
177 //==========================================================================
179 // VBasePlayer::SpawnClient
181 //==========================================================================
182 void VBasePlayer::SpawnClient () {
184 if (PlayerFlags
&PF_Spawned
) GCon
->Log(NAME_Dev
, "Already spawned");
185 if (MO
) GCon
->Log(NAME_Dev
, "Mobj already spawned");
187 for (int i
= 0; i
< Level
->XLevel
->ActiveSequences
.length(); ++i
) {
188 eventClientStartSequence(
189 Level
->XLevel
->ActiveSequences
[i
].Origin
,
190 Level
->XLevel
->ActiveSequences
[i
].OriginId
,
191 Level
->XLevel
->ActiveSequences
[i
].Name
,
192 Level
->XLevel
->ActiveSequences
[i
].ModeNum
);
193 for (int j
= 0; j
< Level
->XLevel
->ActiveSequences
[i
].Choices
.length(); ++j
) {
194 eventClientAddSequenceChoice(
195 Level
->XLevel
->ActiveSequences
[i
].OriginId
,
196 Level
->XLevel
->ActiveSequences
[i
].Choices
[j
]);
200 if (!MO
) Host_Error("Player without Mobj\n");
204 eventClientSetAngles(ViewAngles
);
205 PlayerFlags
&= ~PF_FixAngle
;
207 PlayerFlags
|= PF_Spawned
;
209 if ((GGameInfo
->NetMode
== NM_TitleMap
|| GGameInfo
->NetMode
== NM_Standalone
) && run_open_scripts
) {
210 // start open scripts
211 Level
->XLevel
->Acs
->StartTypedACScripts(SCRIPT_Open
, 0, 0, 0, nullptr, false, false);
215 Level
->XLevel
->Acs
->StartTypedACScripts(SCRIPT_Enter
, 0, 0, 0, MO
, true, false);
216 } else if (sv_map_travel
) {
217 Level
->XLevel
->Acs
->StartTypedACScripts(SCRIPT_Return
, 0, 0, 0, MO
, true, false);
220 if (GGameInfo
->NetMode
< NM_DedicatedServer
|| svs
.num_connected
== sv_load_num_players
) {
222 sv_map_travel
= false;
225 //GCon->Logf(NAME_Debug, "MO Origin: (%g,%g,%g)", MO->Origin.x, MO->Origin.y, MO->Origin.z);
227 // for single play, save immediately into the reborn slot
228 //!if (GGameInfo->NetMode < NM_DedicatedServer) SV_SaveGameToReborn();
232 //==========================================================================
234 // VBasePlayer::Printf
236 //==========================================================================
237 __attribute__((format(printf
,2,3))) void VBasePlayer::Printf (const char *s
, ...) {
239 static char buf
[4096];
242 vsnprintf(buf
, sizeof(buf
), s
, v
);
245 eventClientPrint(buf
);
249 //==========================================================================
251 // VBasePlayer::CenterPrintf
253 //==========================================================================
254 __attribute__((format(printf
,2,3))) void VBasePlayer::CenterPrintf (const char *s
, ...) {
256 static char buf
[4096];
259 vsnprintf(buf
, sizeof(buf
), s
, v
);
262 eventClientCenterPrint(buf
);
266 //==========================================================================
268 // VBasePlayer::ClearReferences
270 //==========================================================================
272 void VBasePlayer::ClearReferences () {
273 Super::ClearReferences();
274 if (LastViewObject && LastViewObject->IsRefToCleanup()) LastViewObject = nullptr;
279 //===========================================================================
281 // VBasePlayer::SetViewState
283 //===========================================================================
284 void VBasePlayer::SetViewState (int position
, VState
*InState
) {
285 if (position
< 0 || position
>= NUMPSPRITES
) return; // sanity check
287 if (position
== PS_WEAPON
) WeaponActionFlags
= 0;
289 if (InState
== VState::GetInvalidState()) InState
= nullptr;
291 VViewState
&VSt
= ViewStates
[position
];
292 VSLOGF("SetViewState(%d): watchcat=%d, vobj=%s, from %s to new %s", position
, ViewStates
[position
].WatchCat
, (_stateRouteSelf
? _stateRouteSelf
->GetClass()->GetName() : "<none>"), (VSt
.State
? *VSt
.State
->Loc
.toStringNoCol() : "<none>"), (InState
? *InState
->Loc
.toStringNoCol() : "<none>"));
296 VEntity
*curwpn
= eventGetReadyWeapon();
297 GCon
->Logf(NAME_Debug
, " curwpn=%s", (curwpn
? curwpn
->GetClass()->GetName() : "<none>"));
301 // the only way we can arrive here is via decorate call
302 if (InState
&& ViewStates
[position
].WatchCat
) {
303 ViewStates
[position
].NewState
= InState
;
304 VSLOGF("SetViewState(%d): recursive, watchcat=%d, from %s to new %s", position
, ViewStates
[position
].WatchCat
, (VSt
.State
? *VSt
.State
->Loc
.toStringNoCol() : "<none>"), (InState
? *InState
->Loc
.toStringNoCol() : "<none>"));
308 if (VState::IsNoJumpState(InState
)) {
310 GCon
->Logf(NAME_Error
, "invalid continuation state in `%s::SetViewState()` (%s)", *GetClass()->GetFullName(), (VSt
.State
? *VSt
.State
->Loc
.toStringNoCol() : "<null>"));
313 InState
= VSt
.State
->NextState
;
318 SetViewStateGuard
guard(this, position
);
321 VState
*state
= InState
;
323 if (!state
) { VSt
.State
= nullptr; break; }
325 if (LastViewObject
!= _stateRouteSelf
) {
327 //GCon->Logf(NAME_Debug, "NEW PSPRITE (%d)! '%s'", position, (_stateRouteSelf ? _stateRouteSelf->GetClass()->GetName() : "<none>"));
328 LastViewObject
= _stateRouteSelf
;
329 ViewStates
[position
].OvlOfsX
= ViewStates
[position
].OvlOfsY
= 0.0f
;
330 // set overlay states for weapon
331 if (position
== PS_WEAPON
) {
332 ViewStateOfsX
= ViewStateOfsY
= 0.0f
;
333 ViewStateBobOfsX
= ViewStateBobOfsY
= 0.0f
;
335 for (int f
= 0; f
< NUMPSPRITES
; ++f
) {
336 if (f
!= PS_WEAPON
) {
337 //GCon->Logf(NAME_Debug, "...clearing offsets for #%d", f);
338 //ViewStates[f].SX = ViewStates[PS_WEAPON].SX;
339 //ViewStates[f].SY = ViewStates[PS_WEAPON].SY;
340 ViewStates
[f
].OvlOfsX
= ViewStates
[f
].OvlOfsY
= 0.0f
;
341 ViewStates
[f
].State
= nullptr;
342 ViewStates
[f
].StateTime
= -1.0f
;
345 static VClass
*WeaponClass
= nullptr;
346 if (!WeaponClass
) WeaponClass
= VClass::FindClass("Weapon");
347 if (_stateRouteSelf
&& _stateRouteSelf
->IsA(WeaponClass
)) {
348 //GCon->Logf(NAME_Warning, "*** NEW DISPLAY STATE: %s", *_stateRouteSelf->GetClass()->GetFullName());
349 VEntity
*wpn
= (VEntity
*)_stateRouteSelf
;
350 SetViewState(PS_WEAPON_OVL
, wpn
->FindState("Display"));
351 SetViewState(PS_WEAPON_OVL_BACK
, wpn
->FindState("DisplayOverlayBack"));
353 //GCon->Logf(NAME_Warning, "*** NEW DISPLAY STATE: EMPTY (%s)", (_stateRouteSelf ? *_stateRouteSelf->GetClass()->GetFullName() : "<none>"));
358 if (++ViewStates
[position
].WatchCat
> 1024 /*|| state->validcount == validcountState*/) {
359 //k8: FIXME! what to do here?
360 GCon
->Logf(NAME_Error
, "WatchCat interrupted `VBasePlayer::SetViewState(%d)` at '%s' (%s)!", position
, *state
->Loc
.toStringNoCol(), (state
->validcount
== validcountState
? "loop" : "timeout"));
361 //VSt.StateTime = 13.0f;
364 state
->validcount
= validcountState
;
366 //if (position == PS_WEAPON) GCon->Logf("*** ... ticking WEAPON (%s)", *state->Loc.toStringNoCol());
368 // remember current sprite and frame
369 UpdateDispFrameFrom(position
, state
);
372 VSt
.StateTime
= state
->Time
; // could be 0
373 if (!isFiniteF(VSt
.StateTime
)) {
374 GCon
->Logf(NAME_Error
, "invalid player state duration at '%s'!", *state
->Loc
.toStringNoCol());
375 VSt
.StateTime
= 1.0f
; // arbitrary value
377 // flash offset cannot be changed from decorate
378 if (position
== PS_WEAPON
) {
379 if (state
->Misc1
) ViewStateOfsX
= state
->Misc1
;
380 if (state
->Misc2
) ViewStateOfsY
= state
->Misc2
-32;
381 } else if (position
!= PS_FLASH
) {
382 if (state
->Misc1
) VSt
.OvlOfsX
= state
->Misc1
;
383 if (state
->Misc2
) VSt
.OvlOfsY
= state
->Misc2
-32;
385 VSt
.OvlOfsX
= VSt
.OvlOfsY
= 0.0f
;
388 VSLOGF("SetViewState(%d): loop: vobj=%s, watchcat=%d, new %s", position
, (_stateRouteSelf
? _stateRouteSelf
->GetClass()->GetName() : "<none>"), ViewStates
[position
].WatchCat
, (VSt
.State
? *VSt
.State
->Loc
.toStringNoCol() : "<none>"));
389 //GCon->Logf(NAME_Debug, "sprite #%d: '%s' %c (ofs: %g, %g)", position, *VSt.DispSpriteName, ('A'+(VSt.DispSpriteFrame&0xff)), VSt.SX, VSt.OfsY);
391 // call action routine
392 if (state
->Function
) {
393 //fprintf(stderr, " VBasePlayer::SetViewState: CALLING '%s'(%s): position=%d; InState=%s\n", *state->Function->GetFullName(), *state->Function->Loc.toStringNoCol(), position, (InState ? *InState->GetFullName() : "<none>"));
394 ViewStates
[position
].NewState
= nullptr;
395 Level
->XLevel
->CallingState
= state
;
396 if (!MO
) Sys_Error("PlayerPawn is dead (wtf?!)");
398 SavedVObjectPtr
svp(&MO
->_stateRouteSelf
);
399 MO
->_stateRouteSelf
= _stateRouteSelf
; // always, 'cause player is The Authority
400 if (!MO
->_stateRouteSelf
) {
401 GCon
->Logf(NAME_Warning
, "Player: viewobject(%d) is not set! state is %s", position
, *state
->Loc
.toStringNoCol());
403 VObject::ExecuteFunctionNoArgs(MO
, state
->Function
); // allow VMT lookups
405 if (!VSt
.State
) break;
406 if (ViewStates
[position
].NewState
&& !VState::IsNoJumpState(ViewStates
[position
].NewState
)) {
407 state
= ViewStates
[position
].NewState
;
408 VSLOGF("SetViewState(%d): current is %s, next is %s", position
, (VSt
.State
? *VSt
.State
->Loc
.toStringNoCol() : "<none>"), *state
->Loc
.toStringNoCol());
409 VSt
.StateTime
= 0.0f
;
413 state
= VSt
.State
->NextState
;
414 } while (VSt
.StateTime
== 0.0f
);
415 VSLOGF("SetViewState(%d): DONE0: watchcat=%d, new %s", position
, ViewStates
[position
].WatchCat
, (VSt
.State
? *VSt
.State
->Loc
.toStringNoCol() : "<none>"));
417 VSLOGF("SetViewState(%d): DONE1: watchcat=%d, new %s", position
, ViewStates
[position
].WatchCat
, (VSt
.State
? *VSt
.State
->Loc
.toStringNoCol() : "<none>"));
418 vassert(ViewStates
[position
].WatchCat
== 0);
421 // object removed itself
422 if (position
== PS_WEAPON
&& developer
) GCon
->Logf(NAME_Dev
, "*** WEAPON removed itself!");
423 VSt
.DispSpriteFrame
= 0;
424 VSt
.DispSpriteName
= NAME_None
;
425 VSt
.StateTime
= -1.0f
;
426 if (position
== PS_WEAPON
) {
427 LastViewObject
= nullptr;
428 ViewStateOfsX
= ViewStateOfsY
= 0.0f
;
429 ViewStateBobOfsX
= ViewStateBobOfsY
= 0.0f
;
430 for (int f
= 0; f
< NUMPSPRITES
; ++f
) {
431 ViewStates
[f
].State
= nullptr;
432 ViewStates
[f
].StateTime
= -1.0f
;
433 //ViewStates[f].SX = ViewStates[f].SY = 0.0f;
434 ViewStates
[f
].OvlOfsX
= ViewStates
[f
].OvlOfsY
= 0.0f
;
441 //==========================================================================
443 // VBasePlayer::WillAdvanceWeaponState
445 //==========================================================================
446 bool VBasePlayer::WillAdvanceWeaponState (float deltaTime
) {
447 const VViewState
&St
= ViewStates
[PS_WEAPON
];
448 if (!St
.State
) return true;
449 const float stime
= St
.StateTime
;
450 if (stime
<= 0.0f
) return true;
451 const int dfchecked
= (eventCheckDoubleFiringSpeed() ? 1 : 0); // call VM only once
452 const float dtime
= deltaTime
*(dfchecked
? 2.0f
: 1.0f
); // [BC] Apply double firing speed
453 return (stime
-dtime
<= 0.0f
);
457 //==========================================================================
459 // VBasePlayer::AdvanceViewStates
461 //==========================================================================
462 bool VBasePlayer::AdvanceViewStates (float deltaTime
) {
463 if (deltaTime
<= 0.0f
) return false;
466 for (unsigned i
= 0; i
< NUMPSPRITES
; ++i
) {
467 VViewState
&St
= ViewStates
[i
];
468 // null state means not active
469 // -1 tic count never changes
470 if (!St
.State
) { if (i
== PS_WEAPON
) res
= true; continue; }
471 if (!isFiniteF(St
.StateTime
)) {
472 GCon
->Logf(NAME_Error
, "invalid player state duration at '%s'!", *St
.State
->Loc
.toStringNoCol());
473 St
.StateTime
= 0.0002f
;
475 if (St
.StateTime
< 0.0f
) { if (i
== PS_WEAPON
) res
= true; St
.StateTime
= -1.0f
; continue; } // force `-1` here just in case
476 if (dfchecked
< 0) dfchecked
= (eventCheckDoubleFiringSpeed() ? 1 : 0); // call VM only once
477 const float dtime
= deltaTime
*(dfchecked
? 2.0f
: 1.0f
); // [BC] Apply double firing speed
478 // drop tic count and possibly change state
479 //GCon->Logf(NAME_Debug, "*** %u:%s:%s: i=%d: StateTime=%g (%g) (nst=%g); delta=%g (%g)", GetUniqueId(), GetClass()->GetName(), *St.State->Loc.toStringShort(), i, St.StateTime, St.StateTime*35.0f, St.StateTime-dtime, dtime, dtime*35.0f);
480 if (St
.StateTime
> 0.0f
) St
.StateTime
-= dtime
;
481 if (i
== PS_WEAPON
&& St
.StateTime
<= 0.0f
) res
= true;
482 while (St
.StateTime
<= 0.0f
) {
483 // this somewhat compensates freestep instability
484 const float tleft
= St
.StateTime
; // "overjump time"
486 SetViewState(i
, St
.State
->NextState
);
487 //if (i == PS_WEAPON) GCon->Logf("AdvanceViewStates: after weapon=%s; route=%s", (LastViewObject[i] ? *LastViewObject[i]->GetClass()->GetFullName() : "<none>"), (_stateRouteSelf ? *_stateRouteSelf->GetClass()->GetFullName() : "<none>"));
488 if (!St
.State
) break;
489 if (St
.StateTime
< 0.0f
) { St
.StateTime
= -1.0f
; break; } // force `-1` here just in case
490 if (St
.StateTime
<= 0.0f
) break; // zero should not end up here, but WatchCat can cause this
491 //GCon->Logf(NAME_Debug, "*** %u:%s:%s: i=%d: tleft=%g; StateTime=%g (%g); rest=%g", GetUniqueId(), GetClass()->GetName(), *St.State->Loc.toStringShort(), i, tleft, St.StateTime, St.StateTime*35.0f, St.StateTime+tleft);
492 St
.StateTime
+= tleft
; // freestep compensation
494 //if (St.State) GCon->Logf(NAME_Debug, " %u:%s:%s: i=%d: END; StateTime=%g (%g); delta=%g (%g)", GetUniqueId(), GetClass()->GetName(), *St.State->Loc.toStringShort(), i, St.StateTime, St.StateTime*35.0f, deltaTime, deltaTime*35.0f);
500 //==========================================================================
502 // VBasePlayer::SetUserInfo
504 //==========================================================================
505 void VBasePlayer::SetUserInfo (VStr info
) {
507 UserInfo
= info
.cloneUnique();
513 //==========================================================================
515 // VBasePlayer::ReadFromUserInfo
517 //==========================================================================
518 void VBasePlayer::ReadFromUserInfo () {
519 if (!sv_loading
) BaseClass
= VStr::atoi(*Info_ValueForKey(UserInfo
, "class"));
520 PlayerName
= Info_ValueForKey(UserInfo
, "name");
521 VStr val
= Info_ValueForKey(UserInfo
, "color");
522 Color
= M_ParseColor(*val
);
523 eventUserinfoChanged();
527 //==========================================================================
529 // VBasePlayer::DoClientStartSound
531 //==========================================================================
532 void VBasePlayer::DoClientStartSound (int SoundId
, TVec Org
, int OriginId
,
533 int Channel
, float Volume
, float Attenuation
,
534 bool Loop
, float ForcePitch
)
537 //if (MO && OriginId == MO->SoundOriginID) GCon->Logf(NAME_Debug, "PLR PLAY (%d) at (%g,%g,%g) (%g,%g,%g)", OriginId, Org.x, Org.y, Org.z, MO->Origin.x, MO->Origin.y, MO->Origin.z);
538 GAudio
->PlaySound(SoundId
, Org
, TVec(0.0f
, 0.0f
, 0.0f
), OriginId
, Channel
, Volume
, Attenuation
, Loop
, ForcePitch
);
543 //==========================================================================
545 // VBasePlayer::DoClientStopSound
547 //==========================================================================
548 void VBasePlayer::DoClientStopSound (int OriginId
, int Channel
) {
550 GAudio
->StopSound(OriginId
, Channel
);
555 //==========================================================================
557 // VBasePlayer::DoClientStartSequence
559 //==========================================================================
560 void VBasePlayer::DoClientStartSequence (TVec Origin
, int OriginId
, VName Name
, int ModeNum
) {
562 GAudio
->StartSequence(OriginId
, Origin
, Name
, ModeNum
);
567 //==========================================================================
569 // VBasePlayer::DoClientAddSequenceChoice
571 //==========================================================================
572 void VBasePlayer::DoClientAddSequenceChoice (int OriginId
, VName Choice
) {
574 GAudio
->AddSeqChoice(OriginId
, Choice
);
579 //==========================================================================
581 // VBasePlayer::DoClientStopSequence
583 //==========================================================================
584 void VBasePlayer::DoClientStopSequence (int OriginId
) {
586 GAudio
->StopSequence(OriginId
);
591 //==========================================================================
593 // VBasePlayer::DoClientPrint
595 //==========================================================================
596 void VBasePlayer::DoClientPrint (VStr AStr
) {
599 if (Str
.IsEmpty()) return;
600 if (Str
[0] == '$') Str
= GLanguage
[*VStr(Str
.ToLower(), 1, Str
.Length()-1)];
601 if (hud_msg_echo
) GCon
->Logf("\034S%s", *Str
);
603 ClGame
->eventAddNotifyMessage(Str
);
607 //==========================================================================
609 // VBasePlayer::DoClientChatPrint
611 //==========================================================================
612 void VBasePlayer::DoClientChatPrint (VStr nick
, VStr text
) {
613 if (text
.IsEmpty()) return;
614 GCon
->Logf(NAME_Chat
, "[%s]: %s", *nick
.RemoveColors().xstrip(), *text
.RemoveColors().xstrip());
615 ClGame
->eventAddChatMessage(nick
, text
);
619 //==========================================================================
621 // VBasePlayer::DoClientCenterPrint
623 //==========================================================================
624 void VBasePlayer::DoClientCenterPrint (VStr Str
) {
627 if (Msg
.IsEmpty()) return;
628 if (Msg
[0] == '$') Msg
= GLanguage
[*VStr(Msg
.ToLower(), 1, Msg
.Length()-1)];
630 //GCon->Log("<-------------------------------->");
631 GCon
->Logf("\034X%s", *Msg
);
632 //GCon->Log("<-------------------------------->");
635 ClGame
->eventAddCenterMessage(Msg
);
639 //==========================================================================
641 // VBasePlayer::DoClientSetAngles
645 //==========================================================================
646 void VBasePlayer::DoClientSetAngles (TAVec Angles
) {
651 enum { ClusterText_Exit
, ClusterText_Enter
};
654 //==========================================================================
658 //==========================================================================
659 static void SetupClusterText (int cttype
, IntermissionText
&im
, const VClusterDef
*CDef
) {
663 VStr text
= (cttype
== ClusterText_Exit
? CDef
->ExitText
: CDef
->EnterText
);
664 if (text
.isEmpty()) return;
666 const bool dotrans
= !!(CDef
->Flags
&(cttype
== ClusterText_Exit
? CLUSTERF_LookupExitText
: CLUSTERF_LookupEnterText
));
667 const bool islump
= !!(CDef
->Flags
&(cttype
== ClusterText_Exit
? CLUSTERF_ExitTextIsLump
: CLUSTERF_EnterTextIsLump
));
669 if (dotrans
) text
= GLanguage
[*text
];
671 //GCon->Logf(NAME_Debug, "cttype=%d; text=\"%s\"", cttype, *text.quote());
672 if (islump
) im
.Flags
|= IntermissionText::IMF_TextIsLump
;
674 if (CDef
->Flags
&CLUSTERF_FinalePic
) {
675 im
.TextFlat
= NAME_None
;
676 im
.TextPic
= CDef
->Flat
;
678 im
.TextFlat
= CDef
->Flat
;
679 im
.TextPic
= NAME_None
;
682 im
.TextMusic
= CDef
->Music
;
686 //==========================================================================
688 // VBasePlayer::DoClientIntermission
690 //==========================================================================
691 void VBasePlayer::DoClientIntermission (VName NextMap
) {
692 IntermissionInfo
&im
= ClGame
->im
;
694 im
.LeaveText
.clear();
695 im
.EnterText
.clear();
697 const VMapInfo
&linfo
= P_GetMapInfo(Level
->XLevel
->MapName
);
698 im
.LeaveMap
= Level
->XLevel
->MapName
;
699 im
.LeaveCluster
= linfo
.Cluster
;
700 im
.LeaveName
= linfo
.GetName();
701 im
.LeaveTitlePatch
= linfo
.ExitTitlePatch
;
702 im
.ExitPic
= linfo
.ExitPic
;
703 im
.InterMusic
= linfo
.InterMusic
;
705 const VMapInfo
&einfo
= P_GetMapInfo(NextMap
);
706 im
.EnterMap
= NextMap
;
707 im
.EnterCluster
= einfo
.Cluster
;
708 im
.EnterName
= einfo
.GetName();
709 im
.EnterTitlePatch
= einfo
.EnterTitlePatch
;
710 im
.EnterPic
= einfo
.EnterPic
;
712 bool didSecret
= false;
713 for (int i
= 0; i
< MAXPLAYERS
; ++i
) {
714 if (GGameInfo
->Players
[i
] && (GGameInfo
->Players
[i
]->PlayerFlags
&PF_ExitedViaSecret
)) {
721 GCon
->Logf(NAME_Debug
, "******************** DoClientIntermission (didSecret=%d) ********************", (int)didSecret
);
722 linfo
.dump("leaving");
723 einfo
.dump("entering");
728 GAudio
->StopAllSequences();
731 if (linfo
.Cluster
!= einfo
.Cluster
|| G_CheckFinale()) {
732 // cluster leaving text
733 // k8: do not show cluster leaving text on secret exit
734 if (linfo
.Cluster
&& (!didSecret
|| G_CheckFinale())) SetupClusterText(ClusterText_Exit
, im
.LeaveText
, P_GetClusterDef(linfo
.Cluster
));
735 // cluster entering text
736 if (einfo
.Cluster
) SetupClusterText(ClusterText_Enter
, im
.EnterText
, P_GetClusterDef(einfo
.Cluster
));
739 VStr mapExitText
= (didSecret
? linfo
.SecretExitText
: linfo
.ExitText
);
740 if (!mapExitText
.isEmpty()) {
741 // this is usually comes from umapinfo, ignore cluster transitions
742 im
.EnterText
.clear();
743 im
.LeaveText
.clear();
744 // single space is special value, means "no text"
745 if (mapExitText
!= " ") {
746 im
.LeaveText
.Text
= mapExitText
;
747 im
.LeaveText
.TextMusic
= im
.InterMusic
;
748 if (linfo
.InterBackdrop
!= NAME_None
) {
749 im
.LeaveText
.TextFlat
= (VStr::strEquCI(*linfo
.InterBackdrop
, "clear") ? NAME_None
: linfo
.InterBackdrop
);
750 im
.LeaveText
.TextPic
= NAME_None
;
752 const VClusterDef
*cd
= P_GetClusterDef(linfo
.Cluster
);
753 if (!cd
|| cd
->Flat
== NAME_None
) cd
= P_GetClusterDef(einfo
.Cluster
);
754 im
.LeaveText
.TextFlat
= (cd
? cd
->Flat
: NAME_None
);
755 im
.LeaveText
.TextPic
= NAME_None
;
760 if (im
.LeaveText
.isActive()) ClGame
->StartIntermissionLeave();
761 else if (im
.EnterText
.isActive()) ClGame
->StartIntermissionEnter();
762 else ClGame
->StartIntermissionLeave(); // anyway
766 //==========================================================================
768 // VBasePlayer::DoClientPause
770 //==========================================================================
771 void VBasePlayer::DoClientPause (bool Paused
) {
774 GGameInfo
->Flags
|= VGameInfo::GIF_Paused
;
775 GAudio
->PauseSound();
777 GGameInfo
->Flags
&= ~VGameInfo::GIF_Paused
;
778 GAudio
->ResumeSound();
784 //==========================================================================
786 // VBasePlayer::DoClientFOV
788 //==========================================================================
789 void VBasePlayer::DoClientFOV (float fov
) {
796 //==========================================================================
798 // VBasePlayer::DoClientSkipIntermission
800 //==========================================================================
801 void VBasePlayer::DoClientSkipIntermission () {
802 ClGame
->ClientFlags
|= VClientGameBase::CF_SkipIntermission
;
806 //==========================================================================
808 // VBasePlayer::DoClientFinale
810 //==========================================================================
811 void VBasePlayer::DoClientFinale (VStr Type
) {
815 ClGame
->StartFinale(*Type
);
819 //==========================================================================
821 // VBasePlayer::DoClientChangeMusic
823 //==========================================================================
824 void VBasePlayer::DoClientChangeMusic (VName Song
) {
825 Level
->SongLump
= Song
;
827 GAudio
->MusicChanged(false/*allowrandom*/);
832 //==========================================================================
834 // VBasePlayer::DoClientSetServerInfo
836 //==========================================================================
837 void VBasePlayer::DoClientSetServerInfo (VStr Key
, VStr Value
) {
838 Info_SetValueForKey(ClGame
->serverinfo
, Key
, Value
);
840 CL_ReadFromServerInfo();
845 //==========================================================================
847 // VBasePlayer::DoClientHudMessage
849 //==========================================================================
850 void VBasePlayer::DoClientHudMessage (VStr Message
, VName Font
, int Type
,
851 int Id
, int Color
, VStr ColorName
, float x
, float y
,
852 int HudWidth
, int HudHeight
, float HoldTime
, float Time1
, float Time2
, float Alpha
)
854 ClGame
->eventAddHudMessage(Message
, Font
, Type
, Id
, Color
, ColorName
,
855 x
, y
, HudWidth
, HudHeight
, HoldTime
, Time1
, Time2
, Alpha
);
859 //==========================================================================
861 // VBasePlayer::WriteViewData
863 // this is called from `SV_SendClientMessages()`
865 //==========================================================================
866 void VBasePlayer::WriteViewData () {
867 // update bam_angles (after teleportation)
868 if (PlayerFlags
&PF_FixAngle
) {
869 PlayerFlags
&= ~PF_FixAngle
;
870 //k8: this should enforce view angles on client (it is done via RPC)
871 //GCon->Logf(NAME_Debug, "FIXANGLES va=(%g,%g,%g)", ViewAngles.pitch, ViewAngles.yaw, ViewAngles.roll);
872 eventClientSetAngles(ViewAngles
);
877 //==========================================================================
879 // VBasePlayer::CallDumpInventory
881 //==========================================================================
882 void VBasePlayer::CallDumpInventory () {
883 VStr name
= "DumpInventory";
884 VMethod
*mt
= GetClass()->FindConCommandMethod(name
);
887 if ((mt
->Flags
&FUNC_Static
) == 0) P_PASS_SELF
;
888 (void)ExecuteFunction(mt
);
892 //==========================================================================
894 // VBasePlayer::ListConCommands
896 // append player commands with the given prefix
898 //==========================================================================
899 void VBasePlayer::ListConCommands (TArray
<VStr
> &list
, VStr pfx
) {
900 for (auto it
= GetClass()->ConCmdListMts
.first(); it
; ++it
) {
901 const char *mtname
= *it
.getValue()->Name
;
903 if (VStr::endsWithNoCase(mtname
, "_AC")) continue;
904 if (!pfx
.isEmpty()) {
905 if (!VStr::startsWithNoCase(mtname
, *pfx
)) continue;
907 list
.append(VStr(mtname
));
912 //==========================================================================
914 // VBasePlayer::IsConCommand
916 //==========================================================================
917 bool VBasePlayer::IsConCommand (VStr name
) {
918 return !!GetClass()->FindConCommandMethod(name
);
922 //==========================================================================
924 // VBasePlayer::ExecConCommand
926 // returns `true` if command was found and executed
927 // uses VCommand command line
929 //==========================================================================
930 bool VBasePlayer::ExecConCommand () {
931 if (VCommand::GetArgC() < 1) return false;
932 VStr name
= VCommand::GetArgV(0);
933 VMethod
*mt
= GetClass()->FindConCommandMethod(name
);
934 if (!mt
) return false;
936 if ((mt
->Flags
&FUNC_Static
) == 0) P_PASS_SELF
;
937 (void)ExecuteFunction(mt
);
942 //==========================================================================
944 // VBasePlayer::ExecConCommandAC
946 // returns `true` if command was found (autocompleter may be still missing)
947 // autocompleter should filter list
949 //==========================================================================
950 bool VBasePlayer::ExecConCommandAC (TArray
<VStr
> &args
, bool newArg
, TArray
<VStr
> &aclist
) {
951 if (args
.length() < 1) return false;
953 if (name
.isEmpty()) return false;
954 VMethod
*mt
= GetClass()->FindConCommandMethodExact(name
+"_AC");
957 // build command line
958 //args.removeAt(0); // remove command name
959 if ((mt
->Flags
&FUNC_Static
) == 0) P_PASS_SELF
;
960 P_PASS_PTR((void *)&args
);
961 P_PASS_INT(newArg
? 1 : 0);
962 P_PASS_PTR((void *)&aclist
);
963 (void)ExecuteFunction(mt
);
966 return !!GetClass()->FindConCommandMethod(name
); // has such cheat?
970 //==========================================================================
972 // VBasePlayer::ResetButtons
974 //==========================================================================
975 void VBasePlayer::ResetButtons () {
981 AcsCurrButtonsPressed
= 0;
985 AcsNextButtonUpdate
= 0;
986 AcsPrevMouseX
= AcsPrevMouseY
= 0;
987 AcsMouseX
= AcsMouseY
= 0;
988 ViewMouseDeltaX
= ViewMouseDeltaY
= 0;
989 RawMouseDeltaX
= RawMouseDeltaY
= 0;
990 // reset "down" flags
991 PlayerFlags
&= ~VBasePlayer::PF_AttackDown
;
992 PlayerFlags
&= ~VBasePlayer::PF_UseDown
;
996 //==========================================================================
1000 //==========================================================================
1002 if (Source
!= SRC_Client
) {
1003 GCon
->Log("SetInfo is not valid from console");
1007 if (Args
.length() != 3) return;
1009 Info_SetValueForKey(Player
->UserInfo
, *Args
[1], *Args
[2]);
1010 Player
->ReadFromUserInfo();
1014 //==========================================================================
1018 //==========================================================================
1019 IMPLEMENT_FUNCTION(VBasePlayer
, get_IsCheckpointSpawn
) {
1020 RET_BOOL(isCheckpointSpawn
);
1023 IMPLEMENT_FUNCTION(VBasePlayer
, IsRunEnabled
) {
1025 RET_BOOL(Self
->IsRunEnabled());
1028 IMPLEMENT_FUNCTION(VBasePlayer
, IsMLookEnabled
) {
1030 RET_BOOL(Self
->IsMLookEnabled());
1033 IMPLEMENT_FUNCTION(VBasePlayer
, IsCrouchEnabled
) {
1035 RET_BOOL(Self
->IsCrouchEnabled());
1038 IMPLEMENT_FUNCTION(VBasePlayer
, IsJumpEnabled
) {
1040 RET_BOOL(Self
->IsJumpEnabled());
1043 IMPLEMENT_FUNCTION(VBasePlayer
, cprint
) {
1044 VStr msg
= PF_FormatString();
1046 Self
->eventClientPrint(*msg
);
1049 IMPLEMENT_FUNCTION(VBasePlayer
, centerprint
) {
1050 VStr msg
= PF_FormatString();
1052 Self
->eventClientCenterPrint(*msg
);
1055 IMPLEMENT_FUNCTION(VBasePlayer
, GetPlayerNum
) {
1057 RET_INT(SV_GetPlayerNum(Self
));
1060 IMPLEMENT_FUNCTION(VBasePlayer
, ClearPlayer
) {
1064 Self
->ForwardMove
= 0;
1069 Self
->AcsCurrButtonsPressed
= 0;
1070 Self
->AcsCurrButtons
= 0;
1071 Self
->AcsButtons
= 0;
1072 Self
->OldButtons
= 0;
1073 Self
->AcsNextButtonUpdate
= 0;
1074 Self
->AcsPrevMouseX
= Self
->AcsPrevMouseY
= 0;
1075 Self
->AcsMouseX
= Self
->AcsMouseY
= 0;
1076 Self
->ViewMouseDeltaX
= Self
->ViewMouseDeltaY
= 0;
1077 Self
->RawMouseDeltaX
= Self
->RawMouseDeltaY
= 0;
1079 Self
->PlayerState
= 0;
1080 Self
->ViewOrg
= TVec(0, 0, 0);
1081 Self
->PlayerFlags
&= ~VBasePlayer::PF_FixAngle
;
1083 Self
->PlayerFlags
&= ~VBasePlayer::PF_AttackDown
;
1084 Self
->PlayerFlags
&= ~VBasePlayer::PF_UseDown
;
1085 Self
->PlayerFlags
&= ~VBasePlayer::PF_AutomapRevealed
;
1086 Self
->PlayerFlags
&= ~VBasePlayer::PF_AutomapShowThings
;
1087 Self
->PlayerFlags
&= ~(VBasePlayer::PF_InverseMouseX
|
1088 VBasePlayer::PF_InverseMouseY
|
1089 VBasePlayer::PF_BlockMouseX
|
1090 VBasePlayer::PF_BlockMouseY
);
1091 Self
->WeaponActionFlags
= 0;
1092 Self
->ExtraLight
= 0;
1093 Self
->FixedColormap
= 0;
1095 Self
->PSpriteSY
= 0;
1096 Self
->PSpriteWeaponLowerPrev
= 0;
1097 Self
->PSpriteWeaponLoweringStartTime
= 0;
1098 Self
->PSpriteWeaponLoweringDuration
= 0;
1099 Self
->LastViewObject
= nullptr;
1100 Self
->Attackers
.reset();
1102 vuint8
*Def
= Self
->GetClass()->Defaults
;
1103 for (VField
*F
= Self
->GetClass()->Fields
; F
; F
= F
->Next
) {
1104 VField::CopyFieldValue(Def
+F
->Ofs
, (vuint8
*)Self
+F
->Ofs
, F
->Type
);
1108 IMPLEMENT_FUNCTION(VBasePlayer
, ResetWeaponActionFlags
) {
1110 Self
->WeaponActionFlags
= 0;
1113 IMPLEMENT_FUNCTION(VBasePlayer
, SetViewObject
) {
1115 vobjGetParamSelf(vobj
);
1116 //if (!vobj) GCon->Logf("RESET VIEW OBJECT; WTF?!");
1117 //GCon->Logf(NAME_Debug, "*** SetViewObject: %s (old: %s)", (vobj ? *vobj->GetClass()->GetFullName() : "<none>"), (Self->_stateRouteSelf ? *Self->_stateRouteSelf->GetClass()->GetFullName() : "<none>"));
1118 if (Self
) Self
->_stateRouteSelf
= vobj
;
1121 IMPLEMENT_FUNCTION(VBasePlayer
, SetViewObjectIfNone
) {
1123 vobjGetParamSelf(vobj
);
1124 //GCon->Logf(NAME_Debug, "*** SetViewObjectIfNone: %s (old: %s)", (vobj ? *vobj->GetClass()->GetFullName() : "<none>"), (Self->_stateRouteSelf ? *Self->_stateRouteSelf->GetClass()->GetFullName() : "<none>"));
1125 if (Self
&& !Self
->_stateRouteSelf
) Self
->_stateRouteSelf
= vobj
;
1128 IMPLEMENT_FUNCTION(VBasePlayer
, SetViewState
) {
1129 //fprintf(stderr, "*** SVS ***\n");
1132 vobjGetParamSelf(position
, InState
);
1133 Self
->SetViewState(position
, InState
);
1136 IMPLEMENT_FUNCTION(VBasePlayer
, AdvanceViewStates
) {
1138 vobjGetParamSelf(deltaTime
);
1139 RET_BOOL(Self
->AdvanceViewStates(deltaTime
));
1142 IMPLEMENT_FUNCTION(VBasePlayer
, WillAdvanceWeaponState
) {
1144 vobjGetParamSelf(deltaTime
);
1145 RET_BOOL(Self
->WillAdvanceWeaponState(deltaTime
));
1148 IMPLEMENT_FUNCTION(VBasePlayer
, DisconnectBot
) {
1150 vassert(Self
->PlayerFlags
&PF_IsBot
);
1151 SV_DropClient(Self
, false);
1154 IMPLEMENT_FUNCTION(VBasePlayer
, ClientStartSound
) {
1157 int OriginId
, Channel
;
1158 float Volume
, Attenuation
;
1161 vobjGetParamSelf(SoundId
, Org
, OriginId
, Channel
, Volume
, Attenuation
, Loop
, ForcePitch
);
1162 Self
->DoClientStartSound(SoundId
, Org
, OriginId
, Channel
, Volume
, Attenuation
, Loop
, ForcePitch
);
1165 IMPLEMENT_FUNCTION(VBasePlayer
, ClientStopSound
) {
1166 int OriginId
, Channel
;
1167 vobjGetParamSelf(OriginId
, Channel
);
1168 Self
->DoClientStopSound(OriginId
, Channel
);
1171 IMPLEMENT_FUNCTION(VBasePlayer
, ClientStartSequence
) {
1176 vobjGetParamSelf(Origin
, OriginId
, Name
, ModeNum
);
1177 Self
->DoClientStartSequence(Origin
, OriginId
, Name
, ModeNum
);
1180 IMPLEMENT_FUNCTION(VBasePlayer
, ClientAddSequenceChoice
) {
1183 vobjGetParamSelf(OriginId
, Choice
);
1184 Self
->DoClientAddSequenceChoice(OriginId
, Choice
);
1187 IMPLEMENT_FUNCTION(VBasePlayer
, ClientStopSequence
) {
1189 vobjGetParamSelf(OriginId
);
1190 Self
->DoClientStopSequence(OriginId
);
1193 IMPLEMENT_FUNCTION(VBasePlayer
, ClientPrint
) {
1195 vobjGetParamSelf(Str
);
1196 Self
->DoClientPrint(Str
);
1199 IMPLEMENT_FUNCTION(VBasePlayer
, ClientChatPrint
) {
1201 vobjGetParamSelf(nick
, str
);
1202 Self
->DoClientChatPrint(nick
, str
);
1205 IMPLEMENT_FUNCTION(VBasePlayer
, ClientCenterPrint
) {
1207 vobjGetParamSelf(Str
);
1208 Self
->DoClientCenterPrint(Str
);
1211 IMPLEMENT_FUNCTION(VBasePlayer
, ClientSetAngles
) {
1213 vobjGetParamSelf(Angles
);
1214 //GCon->Logf(NAME_Debug, "!!! va=(%g,%g,%g); aa=(%g,%g,%g)", Self->ViewAngles.pitch, Self->ViewAngles.yaw, Self->ViewAngles.roll, Angles.pitch, Angles.yaw, Angles.roll);
1215 Self
->DoClientSetAngles(Angles
);
1218 IMPLEMENT_FUNCTION(VBasePlayer
, ClientIntermission
) {
1220 vobjGetParamSelf(NextMap
);
1221 Self
->DoClientIntermission(NextMap
);
1224 IMPLEMENT_FUNCTION(VBasePlayer
, ClientPause
) {
1226 vobjGetParamSelf(Paused
);
1227 Self
->DoClientPause(Paused
);
1230 IMPLEMENT_FUNCTION(VBasePlayer
, ClientSkipIntermission
) {
1232 Self
->DoClientSkipIntermission();
1235 IMPLEMENT_FUNCTION(VBasePlayer
, ClientFinale
) {
1237 vobjGetParamSelf(Type
);
1238 Self
->DoClientFinale(Type
);
1241 IMPLEMENT_FUNCTION(VBasePlayer
, ClientChangeMusic
) {
1243 vobjGetParamSelf(Song
);
1244 Self
->DoClientChangeMusic(Song
);
1247 IMPLEMENT_FUNCTION(VBasePlayer
, ClientSetServerInfo
) {
1249 vobjGetParamSelf(Key
, Value
);
1250 Self
->DoClientSetServerInfo(Key
, Value
);
1253 IMPLEMENT_FUNCTION(VBasePlayer
, ClientHudMessage
) {
1256 int Type
, Id
, Color
;
1259 int HudWidth
, HudHeight
;
1260 float HoldTime
, Time1
, Time2
, Alpha
;
1261 vobjGetParamSelf(Message
, Font
, Type
, Id
, Color
, ColorName
, x
, y
, HudWidth
, HudHeight
, HoldTime
, Time1
, Time2
, Alpha
);
1262 Self
->DoClientHudMessage(Message
, Font
, Type
, Id
, Color
, ColorName
, x
, y
, HudWidth
, HudHeight
, HoldTime
, Time1
, Time2
, Alpha
);
1265 IMPLEMENT_FUNCTION(VBasePlayer
, ClientFOV
) {
1267 vobjGetParamSelf(fov
);
1268 Self
->DoClientFOV(fov
);
1271 IMPLEMENT_FUNCTION(VBasePlayer
, ServerSetUserInfo
) {
1273 vobjGetParamSelf(Info
);
1274 Self
->SetUserInfo(Info
);
1277 // native final void QS_PutInt (string fieldname, int value);
1278 IMPLEMENT_FUNCTION(VBasePlayer
, QS_PutInt
) {
1281 vobjGetParamSelf(name
, value
);
1283 QS_PutValue(QSValue::CreateInt(nullptr, name
, value
));
1286 // native final void QS_PutName (string fieldname, name value);
1287 IMPLEMENT_FUNCTION(VBasePlayer
, QS_PutName
) {
1290 vobjGetParamSelf(name
, value
);
1292 QS_PutValue(QSValue::CreateName(nullptr, name
, value
));
1295 // native final void QS_PutStr (string fieldname, string value);
1296 IMPLEMENT_FUNCTION(VBasePlayer
, QS_PutStr
) {
1299 vobjGetParamSelf(name
, value
);
1301 QS_PutValue(QSValue::CreateStr(nullptr, name
, value
));
1304 // native final void QS_PutFloat (string fieldname, float value);
1305 IMPLEMENT_FUNCTION(VBasePlayer
, QS_PutFloat
) {
1308 vobjGetParamSelf(name
, value
);
1310 QS_PutValue(QSValue::CreateFloat(nullptr, name
, value
));
1313 // native final int QS_GetInt (string fieldname, optional int defvalue);
1314 IMPLEMENT_FUNCTION(VBasePlayer
, QS_GetInt
) {
1316 VOptParamInt
value(0);
1317 vobjGetParamSelf(name
, value
);
1319 QSValue ret
= QS_GetValue(nullptr, name
);
1320 if (ret
.type
!= QSType::QST_Int
) {
1321 if (!value
.specified
) Host_Error("value '%s' not found for player", *name
);
1327 // native final name QS_GetName (string fieldname, optional name defvalue);
1328 IMPLEMENT_FUNCTION(VBasePlayer
, QS_GetName
) {
1330 VOptParamName
value(NAME_None
);
1331 vobjGetParamSelf(name
, value
);
1333 QSValue ret
= QS_GetValue(nullptr, name
);
1334 if (ret
.type
!= QSType::QST_Name
) {
1335 if (!value
.specified
) Host_Error("value '%s' not found for player", *name
);
1341 // native final string QS_GetStr (string fieldname, optional string defvalue);
1342 IMPLEMENT_FUNCTION(VBasePlayer
, QS_GetStr
) {
1344 VOptParamStr
value(VStr::EmptyString
);
1345 vobjGetParamSelf(name
, value
);
1347 QSValue ret
= QS_GetValue(nullptr, name
);
1348 if (ret
.type
!= QSType::QST_Str
) {
1349 if (!value
.specified
) Host_Error("value '%s' not found for player", *name
);
1355 // native final float QS_GetFloat (name fieldname, optional float defvalue);
1356 IMPLEMENT_FUNCTION(VBasePlayer
, QS_GetFloat
) {
1358 VOptParamFloat
value(0.0f
);
1359 vobjGetParamSelf(name
, value
);
1361 QSValue ret
= QS_GetValue(nullptr, name
);
1362 if (ret
.type
!= QSType::QST_Float
) {
1363 if (!value
.specified
) Host_Error("value '%s' not found for player", *name
);
1366 RET_FLOAT(ret
.fval
);
1370 //==========================================================================
1374 //==========================================================================
1375 COMMAND(ChangeWeapon
) {
1376 CMD_FORWARD_TO_SERVER();
1378 if (!Player
) return;
1379 if (Args
.length() < 2) { GCon
->Logf(NAME_Warning
, "ChangeWeapon expects weapon list"); return; }
1381 VEntity
*curwpn
= Player
->eventGetReadyWeapon();
1385 //GCon->Logf("*** CW: active weapon %s", curwpn->GetClass()->GetName());
1386 for (int widx
= 1; widx
< Args
.length(); ++widx
) {
1387 //if (Args[widx].strEquCI(*curwpn->GetClass()->Name)) { nextIndex = widx+1; break; }
1388 if (Player
->eventIsReadyWeaponByName(Args
[widx
], true)) { // allow replacements
1389 //GCon->Logf("*** CW: active weapon at index=%d; str=%s; wpn=%s", widx, *Args[widx], curwpn->GetClass()->GetName());
1396 for (int widx
= 1; widx
< Args
.length(); ++widx
, ++nextIndex
) {
1397 if (nextIndex
>= Args
.length()) nextIndex
= 1;
1398 VEntity
*newwpn
= Player
->eventFindInventoryWeapon(Args
[nextIndex
], true); // allow replacements
1401 GCon->Logf("*** CW: index=%d; str=%s; wpn=%s", nextIndex, *Args[nextIndex], newwpn->GetClass()->GetName());
1403 GCon->Logf(NAME_Warning, "*** CW: index=%d; str=%s; SKIP", nextIndex, *Args[nextIndex]);
1406 if (newwpn
&& Player
->eventSetPendingWeapon(newwpn
)) return;
1411 //==========================================================================
1413 // ConsoleGiveInventory
1415 //==========================================================================
1416 COMMAND(ConsoleGiveInventory
) {
1417 CMD_FORWARD_TO_SERVER();
1419 if (!Player
|| sv
.intermission
|| !GGameInfo
|| GGameInfo
->NetMode
< NM_Standalone
) {
1420 GCon
->Logf(NAME_Error
, "cannot call `ConsoleGiveInventory` when no game is running!");
1423 if (Args
.length() < 2 || Args
[1].length() == 0) return;
1424 int amount
= (Args
.length() > 2 ? VStr::atoi(*Args
[2]) : 1);
1426 Player
->eventConsoleGiveInventory(Args
[1], amount
);
1430 //==========================================================================
1432 // ConsoleTakeInventory
1434 //==========================================================================
1435 COMMAND(ConsoleTakeInventory
) {
1436 CMD_FORWARD_TO_SERVER();
1438 if (!Player
|| sv
.intermission
|| !GGameInfo
|| GGameInfo
->NetMode
< NM_Standalone
) {
1439 GCon
->Logf(NAME_Error
, "cannot call `ConsoleTakeInventory` when no game is running!");
1442 if (Args
.length() < 2 || Args
[1].length() == 0) return;
1443 int amount
= (Args
.length() > 2 ? VStr::atoi(*Args
[2]) : -1);
1445 Player
->eventConsoleTakeInventory(Args
[1], amount
);