libvwad: updated -- vwadwrite: free file buffers on close (otherwise archive creation...
[k8vavoom.git] / source / psim / p_player.cpp
blob83eefba93a20da9c3292185c989a9350f5b7d887
1 //**************************************************************************
2 //**
3 //** ## ## ## ## ## #### #### ### ###
4 //** ## ## ## ## ## ## ## ## ## ## #### ####
5 //** ## ## ## ## ## ## ## ## ## ## ## ## ## ##
6 //** ## ## ######## ## ## ## ## ## ## ## ### ##
7 //** ### ## ## ### ## ## ## ## ## ##
8 //** # ## ## # #### #### ## ##
9 //**
10 //** Copyright (C) 1999-2006 Jānis Legzdiņš
11 //** Copyright (C) 2018-2023 Ketmar Dark
12 //**
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.
16 //**
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.
21 //**
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/>.
24 //**
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"
33 #include "p_entity.h"
34 #include "p_levelinfo.h"
35 #include "p_player.h"
36 #ifdef CLIENT
37 # include "../automap.h"
38 # include "../client/client.h"
39 # include "../sound/sound.h"
40 #endif
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__)
47 #else
48 # define VSLOGF(...) do {} while (0)
49 #endif
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);
66 #ifdef CLIENT
67 extern VCvarF cl_fov;
68 #endif
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] = {
78 PS_WEAPON_OVL_BACK,
79 PS_WEAPON,
80 PS_FLASH,
81 PS_WEAPON_OVL,
85 struct SavedVObjectPtr {
86 VObject **ptr;
87 VObject *saved;
88 SavedVObjectPtr (VObject **aptr) : ptr(aptr), saved(*aptr) {}
89 ~SavedVObjectPtr() { *ptr = saved; }
93 struct SetViewStateGuard {
94 public:
95 VBasePlayer *plr;
96 int pos;
97 public:
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 () {
112 Super::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));
117 abort();
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;
142 if (ShouldSend) {
143 // replication condition is true, the method must be replicated
144 GDemoRecordingContext->ClientConnections[0]->GetPlayerChannel()->SendRpc(Func, this);
148 #ifdef CLIENT
149 if (GGameInfo->NetMode == NM_TitleMap ||
150 GGameInfo->NetMode == NM_Standalone ||
151 (GGameInfo->NetMode == NM_ListenServer && this == cl))
153 return false;
155 #endif
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
164 if (Net) {
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
173 return true;
177 //==========================================================================
179 // VBasePlayer::SpawnClient
181 //==========================================================================
182 void VBasePlayer::SpawnClient () {
183 if (!sv_loading) {
184 if (PlayerFlags&PF_Spawned) GCon->Log(NAME_Dev, "Already spawned");
185 if (MO) GCon->Log(NAME_Dev, "Mobj already spawned");
186 eventSpawnClient();
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]);
199 } else {
200 if (!MO) Host_Error("Player without Mobj\n");
203 ViewAngles.roll = 0;
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);
214 if (!sv_loading) {
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) {
221 sv_loading = false;
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, ...) {
238 va_list v;
239 static char buf[4096];
241 va_start(v, s);
242 vsnprintf(buf, sizeof(buf), s, v);
243 va_end(v);
245 eventClientPrint(buf);
249 //==========================================================================
251 // VBasePlayer::CenterPrintf
253 //==========================================================================
254 __attribute__((format(printf,2,3))) void VBasePlayer::CenterPrintf (const char *s, ...) {
255 va_list v;
256 static char buf[4096];
258 va_start(v, s);
259 vsnprintf(buf, sizeof(buf), s, v);
260 va_end(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>"));
294 #if 0
296 VEntity *curwpn = eventGetReadyWeapon();
297 GCon->Logf(NAME_Debug, " curwpn=%s", (curwpn ? curwpn->GetClass()->GetName() : "<none>"));
299 #endif
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>"));
305 return;
308 if (VState::IsNoJumpState(InState)) {
309 if (!VSt.State) {
310 GCon->Logf(NAME_Error, "invalid continuation state in `%s::SetViewState()` (%s)", *GetClass()->GetFullName(), (VSt.State ? *VSt.State->Loc.toStringNoCol() : "<null>"));
311 InState = nullptr;
312 } else {
313 InState = VSt.State->NextState;
318 SetViewStateGuard guard(this, position);
319 ++validcountState;
321 VState *state = InState;
322 do {
323 if (!state) { VSt.State = nullptr; break; }
325 if (LastViewObject != _stateRouteSelf) {
326 // new object
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;
334 Crosshair = 0;
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"));
352 } else {
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;
362 break;
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);
371 VSt.State = 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;
384 } else {
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;
410 continue;
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);
420 if (!VSt.State) {
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;
464 bool res = false;
465 int dfchecked = -1;
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"
485 St.StateTime = 0.0f;
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);
496 return res;
500 //==========================================================================
502 // VBasePlayer::SetUserInfo
504 //==========================================================================
505 void VBasePlayer::SetUserInfo (VStr info) {
506 if (!sv_loading) {
507 UserInfo = info.cloneUnique();
508 ReadFromUserInfo();
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)
536 #ifdef CLIENT
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);
539 #endif
543 //==========================================================================
545 // VBasePlayer::DoClientStopSound
547 //==========================================================================
548 void VBasePlayer::DoClientStopSound (int OriginId, int Channel) {
549 #ifdef CLIENT
550 GAudio->StopSound(OriginId, Channel);
551 #endif
555 //==========================================================================
557 // VBasePlayer::DoClientStartSequence
559 //==========================================================================
560 void VBasePlayer::DoClientStartSequence (TVec Origin, int OriginId, VName Name, int ModeNum) {
561 #ifdef CLIENT
562 GAudio->StartSequence(OriginId, Origin, Name, ModeNum);
563 #endif
567 //==========================================================================
569 // VBasePlayer::DoClientAddSequenceChoice
571 //==========================================================================
572 void VBasePlayer::DoClientAddSequenceChoice (int OriginId, VName Choice) {
573 #ifdef CLIENT
574 GAudio->AddSeqChoice(OriginId, Choice);
575 #endif
579 //==========================================================================
581 // VBasePlayer::DoClientStopSequence
583 //==========================================================================
584 void VBasePlayer::DoClientStopSequence (int OriginId) {
585 #ifdef CLIENT
586 GAudio->StopSequence(OriginId);
587 #endif
591 //==========================================================================
593 // VBasePlayer::DoClientPrint
595 //==========================================================================
596 void VBasePlayer::DoClientPrint (VStr AStr) {
597 VStr Str(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) {
625 VStr Msg(Str);
627 if (Msg.IsEmpty()) return;
628 if (Msg[0] == '$') Msg = GLanguage[*VStr(Msg.ToLower(), 1, Msg.Length()-1)];
629 if (hud_msg_echo) {
630 //GCon->Log("<-------------------------------->");
631 GCon->Logf("\034X%s", *Msg);
632 //GCon->Log("<-------------------------------->");
635 ClGame->eventAddCenterMessage(Msg);
639 //==========================================================================
641 // VBasePlayer::DoClientSetAngles
643 // called via RPC
645 //==========================================================================
646 void VBasePlayer::DoClientSetAngles (TAVec Angles) {
647 ViewAngles = Angles;
651 enum { ClusterText_Exit, ClusterText_Enter };
654 //==========================================================================
656 // SetupClusterText
658 //==========================================================================
659 static void SetupClusterText (int cttype, IntermissionText &im, const VClusterDef *CDef) {
660 im.clear();
661 if (!CDef) return;
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];
670 im.Text = 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;
677 } else {
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)) {
715 didSecret = true;
716 break;
720 #if 0
721 GCon->Logf(NAME_Debug, "******************** DoClientIntermission (didSecret=%d) ********************", (int)didSecret);
722 linfo.dump("leaving");
723 einfo.dump("entering");
724 #endif
726 #ifdef CLIENT
727 AM_Stop();
728 GAudio->StopAllSequences();
729 #endif
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;
751 } else {
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) {
772 #ifdef CLIENT
773 if (Paused) {
774 GGameInfo->Flags |= VGameInfo::GIF_Paused;
775 GAudio->PauseSound();
776 } else {
777 GGameInfo->Flags &= ~VGameInfo::GIF_Paused;
778 GAudio->ResumeSound();
780 #endif
784 //==========================================================================
786 // VBasePlayer::DoClientFOV
788 //==========================================================================
789 void VBasePlayer::DoClientFOV (float fov) {
790 #ifdef CLIENT
791 cl_fov = fov;
792 #endif
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) {
812 #ifdef CLIENT
813 AM_Stop();
814 #endif
815 ClGame->StartFinale(*Type);
819 //==========================================================================
821 // VBasePlayer::DoClientChangeMusic
823 //==========================================================================
824 void VBasePlayer::DoClientChangeMusic (VName Song) {
825 Level->SongLump = Song;
826 #ifdef CLIENT
827 GAudio->MusicChanged(false/*allowrandom*/);
828 #endif
832 //==========================================================================
834 // VBasePlayer::DoClientSetServerInfo
836 //==========================================================================
837 void VBasePlayer::DoClientSetServerInfo (VStr Key, VStr Value) {
838 Info_SetValueForKey(ClGame->serverinfo, Key, Value);
839 #ifdef CLIENT
840 CL_ReadFromServerInfo();
841 #endif
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);
885 if (!mt) return;
886 // i found her!
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;
902 mtname += 6;
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;
935 // i found her!
936 if ((mt->Flags&FUNC_Static) == 0) P_PASS_SELF;
937 (void)ExecuteFunction(mt);
938 return true;
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;
952 VStr name = args[0];
953 if (name.isEmpty()) return false;
954 VMethod *mt = GetClass()->FindConCommandMethodExact(name+"_AC");
955 if (mt) {
956 // i found her!
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);
964 return true;
966 return !!GetClass()->FindConCommandMethod(name); // has such cheat?
970 //==========================================================================
972 // VBasePlayer::ResetButtons
974 //==========================================================================
975 void VBasePlayer::ResetButtons () {
976 ForwardMove = 0;
977 SideMove = 0;
978 FlyMove = 0;
979 Buttons = 0;
980 Impulse = 0;
981 AcsCurrButtonsPressed = 0;
982 AcsCurrButtons = 0;
983 AcsButtons = 0;
984 OldButtons = 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 //==========================================================================
998 // COMMAND SetInfo
1000 //==========================================================================
1001 COMMAND(SetInfo) {
1002 if (Source != SRC_Client) {
1003 GCon->Log("SetInfo is not valid from console");
1004 return;
1007 if (Args.length() != 3) return;
1009 Info_SetValueForKey(Player->UserInfo, *Args[1], *Args[2]);
1010 Player->ReadFromUserInfo();
1014 //==========================================================================
1016 // Natives
1018 //==========================================================================
1019 IMPLEMENT_FUNCTION(VBasePlayer, get_IsCheckpointSpawn) {
1020 RET_BOOL(isCheckpointSpawn);
1023 IMPLEMENT_FUNCTION(VBasePlayer, IsRunEnabled) {
1024 vobjGetParamSelf();
1025 RET_BOOL(Self->IsRunEnabled());
1028 IMPLEMENT_FUNCTION(VBasePlayer, IsMLookEnabled) {
1029 vobjGetParamSelf();
1030 RET_BOOL(Self->IsMLookEnabled());
1033 IMPLEMENT_FUNCTION(VBasePlayer, IsCrouchEnabled) {
1034 vobjGetParamSelf();
1035 RET_BOOL(Self->IsCrouchEnabled());
1038 IMPLEMENT_FUNCTION(VBasePlayer, IsJumpEnabled) {
1039 vobjGetParamSelf();
1040 RET_BOOL(Self->IsJumpEnabled());
1043 IMPLEMENT_FUNCTION(VBasePlayer, cprint) {
1044 VStr msg = PF_FormatString();
1045 vobjGetParamSelf();
1046 Self->eventClientPrint(*msg);
1049 IMPLEMENT_FUNCTION(VBasePlayer, centerprint) {
1050 VStr msg = PF_FormatString();
1051 vobjGetParamSelf();
1052 Self->eventClientCenterPrint(*msg);
1055 IMPLEMENT_FUNCTION(VBasePlayer, GetPlayerNum) {
1056 vobjGetParamSelf();
1057 RET_INT(SV_GetPlayerNum(Self));
1060 IMPLEMENT_FUNCTION(VBasePlayer, ClearPlayer) {
1061 vobjGetParamSelf();
1063 Self->PClass = 0;
1064 Self->ForwardMove = 0;
1065 Self->SideMove = 0;
1066 Self->FlyMove = 0;
1067 Self->Buttons = 0;
1068 Self->Impulse = 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;
1078 Self->MO = nullptr;
1079 Self->PlayerState = 0;
1080 Self->ViewOrg = TVec(0, 0, 0);
1081 Self->PlayerFlags &= ~VBasePlayer::PF_FixAngle;
1082 Self->Health = 0;
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;
1094 Self->CShift = 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) {
1109 vobjGetParamSelf();
1110 Self->WeaponActionFlags = 0;
1113 IMPLEMENT_FUNCTION(VBasePlayer, SetViewObject) {
1114 VObject *vobj;
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) {
1122 VObject *vobj;
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");
1130 VState *InState;
1131 int position;
1132 vobjGetParamSelf(position, InState);
1133 Self->SetViewState(position, InState);
1136 IMPLEMENT_FUNCTION(VBasePlayer, AdvanceViewStates) {
1137 float deltaTime;
1138 vobjGetParamSelf(deltaTime);
1139 RET_BOOL(Self->AdvanceViewStates(deltaTime));
1142 IMPLEMENT_FUNCTION(VBasePlayer, WillAdvanceWeaponState) {
1143 float deltaTime;
1144 vobjGetParamSelf(deltaTime);
1145 RET_BOOL(Self->WillAdvanceWeaponState(deltaTime));
1148 IMPLEMENT_FUNCTION(VBasePlayer, DisconnectBot) {
1149 vobjGetParamSelf();
1150 vassert(Self->PlayerFlags&PF_IsBot);
1151 SV_DropClient(Self, false);
1154 IMPLEMENT_FUNCTION(VBasePlayer, ClientStartSound) {
1155 int SoundId;
1156 TVec Org;
1157 int OriginId, Channel;
1158 float Volume, Attenuation;
1159 bool Loop;
1160 float ForcePitch;
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) {
1172 TVec Origin;
1173 int OriginId;
1174 VName Name;
1175 int ModeNum;
1176 vobjGetParamSelf(Origin, OriginId, Name, ModeNum);
1177 Self->DoClientStartSequence(Origin, OriginId, Name, ModeNum);
1180 IMPLEMENT_FUNCTION(VBasePlayer, ClientAddSequenceChoice) {
1181 int OriginId;
1182 VName Choice;
1183 vobjGetParamSelf(OriginId, Choice);
1184 Self->DoClientAddSequenceChoice(OriginId, Choice);
1187 IMPLEMENT_FUNCTION(VBasePlayer, ClientStopSequence) {
1188 int OriginId;
1189 vobjGetParamSelf(OriginId);
1190 Self->DoClientStopSequence(OriginId);
1193 IMPLEMENT_FUNCTION(VBasePlayer, ClientPrint) {
1194 VStr Str;
1195 vobjGetParamSelf(Str);
1196 Self->DoClientPrint(Str);
1199 IMPLEMENT_FUNCTION(VBasePlayer, ClientChatPrint) {
1200 VStr nick, str;
1201 vobjGetParamSelf(nick, str);
1202 Self->DoClientChatPrint(nick, str);
1205 IMPLEMENT_FUNCTION(VBasePlayer, ClientCenterPrint) {
1206 VStr Str;
1207 vobjGetParamSelf(Str);
1208 Self->DoClientCenterPrint(Str);
1211 IMPLEMENT_FUNCTION(VBasePlayer, ClientSetAngles) {
1212 TAVec Angles;
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) {
1219 VName NextMap;
1220 vobjGetParamSelf(NextMap);
1221 Self->DoClientIntermission(NextMap);
1224 IMPLEMENT_FUNCTION(VBasePlayer, ClientPause) {
1225 bool Paused;
1226 vobjGetParamSelf(Paused);
1227 Self->DoClientPause(Paused);
1230 IMPLEMENT_FUNCTION(VBasePlayer, ClientSkipIntermission) {
1231 vobjGetParamSelf();
1232 Self->DoClientSkipIntermission();
1235 IMPLEMENT_FUNCTION(VBasePlayer, ClientFinale) {
1236 VStr Type;
1237 vobjGetParamSelf(Type);
1238 Self->DoClientFinale(Type);
1241 IMPLEMENT_FUNCTION(VBasePlayer, ClientChangeMusic) {
1242 VName Song;
1243 vobjGetParamSelf(Song);
1244 Self->DoClientChangeMusic(Song);
1247 IMPLEMENT_FUNCTION(VBasePlayer, ClientSetServerInfo) {
1248 VStr Key, Value;
1249 vobjGetParamSelf(Key, Value);
1250 Self->DoClientSetServerInfo(Key, Value);
1253 IMPLEMENT_FUNCTION(VBasePlayer, ClientHudMessage) {
1254 VStr Message;
1255 VName Font;
1256 int Type, Id, Color;
1257 VStr ColorName;
1258 float x, y;
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) {
1266 float fov;
1267 vobjGetParamSelf(fov);
1268 Self->DoClientFOV(fov);
1271 IMPLEMENT_FUNCTION(VBasePlayer, ServerSetUserInfo) {
1272 VStr Info;
1273 vobjGetParamSelf(Info);
1274 Self->SetUserInfo(Info);
1277 // native final void QS_PutInt (string fieldname, int value);
1278 IMPLEMENT_FUNCTION(VBasePlayer, QS_PutInt) {
1279 VStr name;
1280 int value;
1281 vobjGetParamSelf(name, value);
1282 (void)Self;
1283 QS_PutValue(QSValue::CreateInt(nullptr, name, value));
1286 // native final void QS_PutName (string fieldname, name value);
1287 IMPLEMENT_FUNCTION(VBasePlayer, QS_PutName) {
1288 VStr name;
1289 VName value;
1290 vobjGetParamSelf(name, value);
1291 (void)Self;
1292 QS_PutValue(QSValue::CreateName(nullptr, name, value));
1295 // native final void QS_PutStr (string fieldname, string value);
1296 IMPLEMENT_FUNCTION(VBasePlayer, QS_PutStr) {
1297 VStr name;
1298 VStr value;
1299 vobjGetParamSelf(name, value);
1300 (void)Self;
1301 QS_PutValue(QSValue::CreateStr(nullptr, name, value));
1304 // native final void QS_PutFloat (string fieldname, float value);
1305 IMPLEMENT_FUNCTION(VBasePlayer, QS_PutFloat) {
1306 VStr name;
1307 float value;
1308 vobjGetParamSelf(name, value);
1309 (void)Self;
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) {
1315 VStr name;
1316 VOptParamInt value(0);
1317 vobjGetParamSelf(name, value);
1318 (void)Self;
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);
1322 ret.ival = value;
1324 RET_INT(ret.ival);
1327 // native final name QS_GetName (string fieldname, optional name defvalue);
1328 IMPLEMENT_FUNCTION(VBasePlayer, QS_GetName) {
1329 VStr name;
1330 VOptParamName value(NAME_None);
1331 vobjGetParamSelf(name, value);
1332 (void)Self;
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);
1336 ret.nval = value;
1338 RET_NAME(ret.nval);
1341 // native final string QS_GetStr (string fieldname, optional string defvalue);
1342 IMPLEMENT_FUNCTION(VBasePlayer, QS_GetStr) {
1343 VStr name;
1344 VOptParamStr value(VStr::EmptyString);
1345 vobjGetParamSelf(name, value);
1346 (void)Self;
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);
1350 ret.sval = value;
1352 RET_STR(ret.sval);
1355 // native final float QS_GetFloat (name fieldname, optional float defvalue);
1356 IMPLEMENT_FUNCTION(VBasePlayer, QS_GetFloat) {
1357 VStr name;
1358 VOptParamFloat value(0.0f);
1359 vobjGetParamSelf(name, value);
1360 (void)Self;
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);
1364 ret.fval = value;
1366 RET_FLOAT(ret.fval);
1370 //==========================================================================
1372 // ChangeWeapon
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();
1383 int nextIndex = 1;
1384 if (curwpn) {
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());
1390 nextIndex = widx+1;
1391 break;
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
1400 if (newwpn) {
1401 GCon->Logf("*** CW: index=%d; str=%s; wpn=%s", nextIndex, *Args[nextIndex], newwpn->GetClass()->GetName());
1402 } else {
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);