/* =========================================================================== Copyright (C) 2023 the OpenMoHAA team This file is part of OpenMoHAA source code. OpenMoHAA source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. OpenMoHAA source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenMoHAA source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // sentient.cpp: Base class of entity that can carry other entities, and use weapons. // #include "g_local.h" #include "g_phys.h" #include "entity.h" #include "sentient.h" #include "weapon.h" #include "weaputils.h" #include "scriptmaster.h" #include "scriptexception.h" #include "ammo.h" #include "armor.h" #include "misc.h" #include "inventoryitem.h" #include "player.h" #include "actor.h" #include "decals.h" #include "g_spawn.h" #include "object.h" #include "../qcommon/tiki.h" #include "weapturret.h" Event EV_Sentient_ReloadWeapon ( "reloadweapon", EV_DEFAULT, "s", "hand", "Reloads the weapon in the specified hand", EV_NORMAL ); Event EV_Sentient_Attack ( "fire", EV_DEFAULT, "SS", "hand mode", "Fires the weapon in the specified hand.", EV_NORMAL ); Event EV_Sentient_StopFire ( "stopfire", EV_DEFAULT, "s", "hand", "Stops the firing of the weapon in the specified hand.", EV_NORMAL ); Event EV_Sentient_Charge ( "charge", EV_DEFAULT, "s", "hand", "Starts the charging of the weapon in the specified hand", EV_NORMAL ); Event EV_Sentient_ReleaseAttack ( "releasefire", EV_DEFAULT, "f", "fireholdtime", "Releases the attack in the time specified.", EV_NORMAL ); Event EV_Sentient_GiveWeapon ( "weapon", EV_DEFAULT, "s", "weapon_modelname", "Gives the sentient the weapon specified.", EV_NORMAL ); Event EV_Sentient_SetWeaponIdleState ( "setweaponidlestate", EV_DEFAULT, "i", "state", "set the idle state of the given weapon.", EV_NORMAL ); Event EV_Sentient_PingForMines ( "pingformines", EV_DEFAULT, NULL, NULL, "actively uncover mines nearby.", EV_NORMAL ); Event EV_Sentient_ForceLandmineMeasure ( "forcelandminemeasure", EV_DEFAULT, NULL, NULL, "Force a remeasurement to all landmines", EV_NORMAL ); Event EV_Sentient_Take ( "take", EV_DEFAULT, "s", "item_name", "Takes away the specified item from the sentient.", EV_NORMAL ); Event EV_Sentient_TakeAll ( "takeall", EV_DEFAULT, NULL, NULL, "Clears out the sentient's entire inventory.", EV_NORMAL ); Event EV_Sentient_GiveAmmo ( "ammo", EV_DEFAULT, "si", "type amount", "Gives the sentient some ammo.", EV_NORMAL ); Event EV_Sentient_GiveArmor ( "armor", EV_DEFAULT, "si", "type amount", "Gives the sentient some armor.", EV_NORMAL ); Event EV_Sentient_GiveItem ( "item", EV_DEFAULT, "si", "type amount", "Gives the sentient the specified amount of the specified item.", EV_NORMAL ); Event EV_Sentient_GiveDynItem ( "givedynitem", EV_DEFAULT, "ss", "model bonename", "Pass the args to the item.", EV_NORMAL ); Event EV_Sentient_GiveTargetname ( "give", EV_DEFAULT, "s", "name", "Gives the sentient the targeted item.", EV_NORMAL ); Event EV_Sentient_UseItem ( "use", EV_CONSOLE, "si", "name weapon_hand", "Use the specified weapon or item in the hand chosen (optional).", EV_NORMAL ); Event EV_Sentient_SetBloodModel ( "bloodmodel", EV_DEFAULT, "s", "bloodModel", "set the model to be used when showing blood", EV_NORMAL ); Event EV_Sentient_TurnOffShadow ( "noshadow", EV_DEFAULT, NULL, NULL, "Turns off the shadow for this sentient.", EV_NORMAL ); Event EV_Sentient_TurnOnShadow ( "shadow", EV_DEFAULT, NULL, NULL, "Turns on the shadow for this sentient.", EV_NORMAL ); Event EV_Sentient_JumpXY ( "jumpxy", EV_DEFAULT, "fff", "forwardmove sidemove speed", "Makes the sentient jump.", EV_NORMAL ); Event EV_Sentient_MeleeAttackStart ( "meleeattackstart", EV_DEFAULT, NULL, NULL, "Is the start of the sentient's melee attack.", EV_NORMAL ); Event EV_Sentient_MeleeAttackEnd ( "meleeattackend", EV_DEFAULT, NULL, NULL, "Is the end of the sentient's melee attack.", EV_NORMAL ); Event EV_Sentient_BlockStart ( "blockstart", EV_DEFAULT, NULL, NULL, "Is the start of the sentient's block.", EV_NORMAL ); Event EV_Sentient_BlockEnd ( "blockend", EV_DEFAULT, NULL, NULL, "Is the end of the sentient's block.", EV_NORMAL ); Event EV_Sentient_StunStart ( "stunstart", EV_DEFAULT, NULL, NULL, "Is the start of the sentient's stun.", EV_NORMAL ); Event EV_Sentient_StunEnd ( "stunend", EV_DEFAULT, NULL, NULL, "Is the end of the sentient's stun.", EV_NORMAL ); Event EV_Sentient_SetMouthAngle ( "mouthangle", EV_DEFAULT, "f", "mouth_angle", "Sets the mouth angle of the sentient.", EV_NORMAL ); Event EV_Sentient_SetMaxMouthAngle ( "maxmouthangle", EV_DEFAULT, "f", "max_mouth_angle", "Sets the max mouth angle.", EV_NORMAL ); Event EV_Sentient_OnFire ( "onfire", EV_DEFAULT, NULL, NULL, "Called every frame when the sentient is on fire.", EV_NORMAL ); Event EV_Sentient_StopOnFire ( "stoponfire", EV_DEFAULT, NULL, NULL, "Stops the sentient from being on fire.", EV_NORMAL ); Event EV_Sentient_SpawnBloodyGibs ( "spawnbloodygibs", EV_DEFAULT, "IF", "number_of_gibs scale", "Spawns some bloody generic gibs.", EV_NORMAL ); Event EV_Sentient_SetMaxGibs ( "maxgibs", EV_DEFAULT, "i", "max_number_of_gibs", "Sets the maximum amount of generic gibs this sentient will spawn when hit.", EV_NORMAL ); Event EV_Sentient_CheckAnimations ( "checkanims", EV_DEFAULT, NULL, NULL, "Check the animations in the .tik file versus the statefile", EV_NORMAL ); Event EV_Sentient_DeactivateWeapon ( "deactivateweapon", EV_DEFAULT, "s", "side", "Deactivate the weapon in the specified hand.", EV_NORMAL ); Event EV_Sentient_ActivateNewWeapon ( "activatenewweapon", EV_DEFAULT, NULL, NULL, "Activate the new weapon specified by useWeapon. handsurf allows specifying which hand to use for the player", EV_NORMAL ); Event EV_Sentient_PutawayWeapon ( "putawayweapon", EV_DEFAULT, "s", "whichHand", "Put away or deactivate the current weapon, whichHand can be left or right.", EV_NORMAL ); Event EV_Sentient_Weapon ( "weaponcommand", EV_DEFAULT, "sSSSSSSS", "hand arg1 arg2 arg3 arg4 arg5 arg6 arg7", "Pass the args to the active weapon in the specified hand", EV_NORMAL ); Event EV_Sentient_UseWeaponClass ( "useweaponclass", EV_CONSOLE, "sI", "name weapon_hand", "Use the weapon of the specified class in the hand chosen (optional).", EV_NORMAL ); Event EV_Sentient_German ( "german", EV_DEFAULT, NULL, NULL, "Makes the sentient a German.", EV_NORMAL ); Event EV_Sentient_American ( "american", EV_DEFAULT, NULL, NULL, "Makes the sentient an American.", EV_NORMAL ); Event EV_Sentient_GetTeam ( "team", EV_DEFAULT, NULL, NULL, "returns 'german' or 'american'", EV_GETTER ); Event EV_Sentient_SetDamageMult ( "damagemult", EV_DEFAULT, "if", "location multiplier", "Sets the damage multiplier for a particular body location", EV_NORMAL ); Event EV_Sentient_UseLastWeapon ( "uselast", EV_DEFAULT, NULL, NULL, "Activates the last active weapon", EV_NORMAL ); Event EV_Sentient_ToggleItemUse ( "toggleitem", EV_CONSOLE, NULL, NULL, "Toggles the use of the player's item (first item if he has multiple)", EV_NORMAL ); Event EV_Sentient_GetThreatBias ( "threatbias", EV_DEFAULT, NULL, NULL, "Gets the threat bias for this player / AI", EV_GETTER ); Event EV_Sentient_SetThreatBias ( "threatbias", EV_DEFAULT, "i", "bias", "Sets the threat bias for this player / AI", EV_SETTER ); Event EV_Sentient_SetThreatBias2 ( "threatbias", EV_DEFAULT, "i", "bias", "Sets the threat bias for this player / AI", EV_NORMAL ); Event EV_Sentient_SetupHelmet ( "sethelmet", EV_DEFAULT, "sffss", "tikifile popspeed dmgmult surfacename [optional_additional_surface_name]", "Gives the sentient a helmet and sets the needed info for it", EV_NORMAL ); Event EV_Sentient_PopHelmet ( "pophelmet", EV_DEFAULT, NULL, NULL, "Pops a sentient's helmet off if he's got one", EV_NORMAL ); Event EV_Sentient_DropItems ( "dropitems", EV_DEFAULT, NULL, NULL, "drops inventory items", EV_NORMAL ); Event EV_Sentient_DontDropWeapons ( "dontdropweapons", EV_DEFAULT, "B", "dont_drop", "Make the sentient not drop weapons", EV_NORMAL ); Event EV_Sentient_ForceDropWeapon ( "forcedropweapon", EV_DEFAULT, NULL, NULL, "Force the sentient to drop weapons no matter what level.nodropweapon is.", EV_NORMAL ); Event EV_Sentient_ForceDropWeapon2 ( "forcedropweapon", EV_DEFAULT, NULL, NULL, "Force the sentient to drop weapons no matter what level.nodropweapon is.", EV_SETTER ); Event EV_Sentient_ForceDropHealth ( "forcedropweapon", EV_DEFAULT, NULL, NULL, "Force the sentient to drop health no matter what level.nodrophealth is.", EV_NORMAL ); Event EV_Sentient_ForceDropHealth2 ( "forcedropweapon", EV_DEFAULT, NULL, NULL, "Force the sentient to drop health no matter what level.nodrophealth is.", EV_SETTER ); Event EV_Sentient_GetForceDropHealth ( "forcedrophealth", EV_DEFAULT, NULL, NULL, "Get if the sentient is forced to drop health no matter what level.nodrophealth is.", EV_GETTER ); Event EV_Sentient_GetForceDropWeapon ( "forcedropweapon", EV_DEFAULT, NULL, NULL, "Get if the sentient is forced to drop health no matter what level.nodrophealth is.", EV_GETTER ); // // Added in OPM // Event EV_Sentient_GetNewActiveWeap ( "getnewactiveweap", EV_DEFAULT, NULL, NULL, "gets new active weapon", EV_RETURN ); Event EV_Sentient_GetActiveWeap ( "getactiveweap", EV_DEFAULT, "i", "weaponhand", "gets currently active weapon in a given hand", EV_RETURN ); Event EV_Sentient_Client_Landing ( "_client_landing", EV_DEFAULT, "FI", "fVolume iEquipment", "Play a landing sound that is appropriate to the surface we are landing on\n" ); CLASS_DECLARATION(Animate, Sentient, NULL) { {&EV_Sentient_ReloadWeapon, &Sentient::ReloadWeapon }, {&EV_Sentient_Attack, &Sentient::FireWeapon }, {&EV_Sentient_StopFire, &Sentient::StopFireWeapon }, {&EV_Sentient_Charge, &Sentient::ChargeWeapon }, {&EV_Sentient_ReleaseAttack, &Sentient::ReleaseFireWeapon }, {&EV_Sentient_GiveAmmo, &Sentient::EventGiveAmmo }, {&EV_Sentient_GiveWeapon, &Sentient::EventGiveItem }, {&EV_Sentient_GiveArmor, &Sentient::EventGiveItem }, {&EV_Sentient_GiveItem, &Sentient::EventGiveItem }, {&EV_Sentient_GiveDynItem, &Sentient::EventGiveDynItem }, {&EV_Sentient_UseItem, &Sentient::EventUseItem }, {&EV_Sentient_Take, &Sentient::EventTake }, {&EV_Sentient_TakeAll, &Sentient::EventFreeInventory }, {&EV_Sentient_SetBloodModel, &Sentient::SetBloodModel }, {&EV_Sentient_GiveTargetname, &Sentient::EventGiveTargetname }, {&EV_Sentient_SetWeaponIdleState, &Sentient::EventSetWeaponIdleState }, {&EV_Sentient_PingForMines, &Sentient::EventPingForMines }, {&EV_Sentient_ForceLandmineMeasure, &Sentient::EventForceLandmineMeasure }, {&EV_Damage, &Sentient::ArmorDamage }, {&EV_Sentient_TurnOffShadow, &Sentient::TurnOffShadow }, {&EV_Sentient_TurnOnShadow, &Sentient::TurnOnShadow }, {&EV_Sentient_JumpXY, &Sentient::JumpXY }, {&EV_Sentient_MeleeAttackStart, &Sentient::MeleeAttackStart }, {&EV_Sentient_MeleeAttackEnd, &Sentient::MeleeAttackEnd }, {&EV_Sentient_BlockStart, &Sentient::BlockStart }, {&EV_Sentient_BlockEnd, &Sentient::BlockEnd }, {&EV_Sentient_StunStart, &Sentient::StunStart }, {&EV_Sentient_StunEnd, &Sentient::StunEnd }, {&EV_Sentient_SetMaxMouthAngle, &Sentient::SetMaxMouthAngle }, {&EV_Sentient_OnFire, &Sentient::OnFire }, {&EV_Sentient_StopOnFire, &Sentient::StopOnFire }, {&EV_Sentient_SpawnBloodyGibs, &Sentient::SpawnBloodyGibs }, {&EV_Sentient_SetMaxGibs, &Sentient::SetMaxGibs }, {&EV_Sentient_CheckAnimations, &Sentient::CheckAnimations }, {&EV_Sentient_German, &Sentient::EventGerman }, {&EV_Sentient_American, &Sentient::EventAmerican }, {&EV_Sentient_GetTeam, &Sentient::EventGetTeam }, {&EV_Sentient_SetDamageMult, &Sentient::SetDamageMult }, {&EV_Sentient_SetupHelmet, &Sentient::EventSetupHelmet }, {&EV_Sentient_PopHelmet, &Sentient::EventPopHelmet }, {&EV_Sentient_GetThreatBias, &Sentient::EventGetThreatBias }, {&EV_Sentient_SetThreatBias, &Sentient::EventSetThreatBias }, {&EV_Sentient_SetThreatBias2, &Sentient::EventSetThreatBias }, {&EV_Sentient_DeactivateWeapon, &Sentient::EventDeactivateWeapon }, {&EV_Sentient_ActivateNewWeapon, &Sentient::ActivateNewWeapon }, {&EV_Sentient_PutawayWeapon, &Sentient::PutawayWeapon }, {&EV_Sentient_Weapon, &Sentient::WeaponCommand }, {&EV_Sentient_UseWeaponClass, &Sentient::EventUseWeaponClass }, {&EV_Sentient_UseLastWeapon, &Sentient::EventActivateLastActiveWeapon}, {&EV_Sentient_ToggleItemUse, &Sentient::EventToggleItemUse }, {&EV_Sentient_DropItems, &Sentient::EventDropItems }, {&EV_Sentient_DontDropWeapons, &Sentient::EventDontDropWeapons }, {&EV_Sentient_ForceDropHealth, &Sentient::EventForceDropHealth }, {&EV_Sentient_ForceDropHealth2, &Sentient::EventForceDropHealth }, {&EV_Sentient_GetForceDropHealth, &Sentient::EventGetForceDropHealth }, {&EV_Sentient_ForceDropWeapon, &Sentient::EventForceDropWeapon }, {&EV_Sentient_ForceDropWeapon2, &Sentient::EventForceDropWeapon }, {&EV_Sentient_GetForceDropWeapon, &Sentient::EventGetForceDropWeapon }, {&EV_Sentient_GetActiveWeap, &Sentient::GetActiveWeap }, {&EV_Sentient_GetNewActiveWeap, &Sentient::GetNewActiveWeapon }, {&EV_Sentient_Client_Landing, &Sentient::EventClientLanding }, {NULL, NULL } }; Container SentientList; void Sentient::EventGiveDynItem(Event *ev) { str tikiname; int tagnum; Vector offset; DynItem *item; item = new DynItem(); tikiname = ev->GetString(1); item->m_attachPrime = ev->GetString(2); item->setModel(tikiname); tagnum = gi.Tag_NumForName(edict->tiki, item->m_attachPrime.c_str()); if (tagnum >= 0 && !item->attach(entnum, tagnum, qtrue, offset)) { // invalid tagnum delete item; return; } item->setSolidType(SOLID_BBOX); item->setMoveType(MOVETYPE_BOUNCE); item->takedamage = DAMAGE_YES; item->ProcessPendingEvents(); } Sentient::Sentient() : mAccuracy(0.2f) , m_bIsAnimal(false) { SentientList.AddObject((Sentient *)this); entflags |= ECF_SENTIENT; m_bOvercookDied = false; if (LoadingSavegame) { return; } viewheight = 0; means_of_death = MOD_NONE; LMRF = 0; in_melee_attack = false; in_block = false; in_stun = false; on_fire = 0; on_fire_stop_time = 0; next_catch_on_fire_time = 0; on_fire_tagnums[0] = -1; on_fire_tagnums[1] = -1; on_fire_tagnums[2] = -1; attack_blocked_time = 0; m_fHelmetSpeed = 0; inventory.ClearObjectList(); m_pNextSquadMate = this; m_pPrevSquadMate = this; m_Enemy.Clear(); m_fPlayerSightLevel = 0; newWeapon = NULL; eyeposition = Vector(0, 0, 64); charge_start_time = 0; poweruptype = 0; poweruptimer = 0; // do better lighting on all sentients edict->s.renderfx |= RF_EXTRALIGHT; edict->s.renderfx |= RF_SHADOW; // sentients have precise shadows edict->s.renderfx |= RF_SHADOW_PRECISE; m_vViewVariation = Vector(0, 0, 0); for (int i = 0; i < MAX_ACTIVE_WEAPONS; i++) { activeWeaponList[i] = NULL; } in_melee_attack = false; in_block = false; in_stun = false; attack_blocked = qfalse; max_mouth_angle = 10; // touch triggers by default flags |= FL_TOUCH_TRIGGERS; on_fire = false; max_gibs = 0; next_bleed_time = 0; ClearNewActiveWeapon(); newActiveWeapon.weapon = NULL; holsteredWeapon = NULL; weapons_holstered_by_code = false; lastActiveWeapon.weapon = NULL; edict->s.eFlags |= EF_UNARMED; m_pVehicle.Clear(); m_pTurret.Clear(); m_pLadder.Clear(); m_iAttackerCount = 0; m_pLastAttacker.Clear(); m_bIsDisguised = false; m_bHasDisguise = false; m_ShowPapersTime = 0; m_iLastHitTime = 0; m_Team = TEAM_AMERICAN; m_iThreatBias = 0; m_bFootOnGround_Right = true; m_bFootOnGround_Left = true; iNextLandTime = 0; m_bDontDropWeapons = false; if (g_realismmode->integer) { m_fDamageMultipliers[HITLOC_HEAD] = 5.0f; m_fDamageMultipliers[HITLOC_HELMET] = 5.0f; m_fDamageMultipliers[HITLOC_NECK] = 5.0f; m_fDamageMultipliers[HITLOC_TORSO_UPPER] = 1.0f; m_fDamageMultipliers[HITLOC_TORSO_MID] = 0.95f; m_fDamageMultipliers[HITLOC_TORSO_LOWER] = 0.9f; m_fDamageMultipliers[HITLOC_PELVIS] = 0.85f; } else { m_fDamageMultipliers[HITLOC_HEAD] = 4.0f; m_fDamageMultipliers[HITLOC_HELMET] = 4.0f; m_fDamageMultipliers[HITLOC_NECK] = 4.0f; m_fDamageMultipliers[HITLOC_TORSO_UPPER] = 1.0f; m_fDamageMultipliers[HITLOC_TORSO_MID] = 1.0f; m_fDamageMultipliers[HITLOC_TORSO_LOWER] = 1.0f; m_fDamageMultipliers[HITLOC_PELVIS] = 0.9f; } m_fDamageMultipliers[HITLOC_R_ARM_UPPER] = 0.8f; m_fDamageMultipliers[HITLOC_L_ARM_UPPER] = 0.8f; m_fDamageMultipliers[HITLOC_R_LEG_UPPER] = 0.8f; m_fDamageMultipliers[HITLOC_L_LEG_UPPER] = 0.8f; m_fDamageMultipliers[HITLOC_R_ARM_LOWER] = 0.6f; m_fDamageMultipliers[HITLOC_L_ARM_LOWER] = 0.6f; m_fDamageMultipliers[HITLOC_R_LEG_LOWER] = 0.6f; m_fDamageMultipliers[HITLOC_L_LEG_LOWER] = 0.6f; m_fDamageMultipliers[HITLOC_R_HAND] = 0.5f; m_fDamageMultipliers[HITLOC_L_HAND] = 0.5f; m_fDamageMultipliers[HITLOC_R_FOOT] = 0.5f; m_fDamageMultipliers[HITLOC_L_FOOT] = 0.5f; m_PrevSentient = m_NextSentient = NULL; m_bForceDropHealth = false; m_bForceDropWeapon = false; Link(); } Sentient::~Sentient() { Unlink(); DisbandSquadMate(this); SentientList.RemoveObject((Sentient *)this); FreeInventory(); entflags &= ~ECF_SENTIENT; } void Sentient::Link() { m_PrevSentient = NULL; m_NextSentient = level.m_HeadSentient[m_Team]; if (m_NextSentient) { m_NextSentient->m_PrevSentient = this; } level.m_HeadSentient[m_Team] = this; } void Sentient::Unlink() { if (m_NextSentient) { m_NextSentient->m_PrevSentient = m_PrevSentient; } if (m_PrevSentient) { m_PrevSentient->m_NextSentient = m_NextSentient; } else { level.m_HeadSentient[this->m_Team] = m_NextSentient; } m_NextSentient = m_PrevSentient = NULL; } Vector Sentient::EyePosition(void) { return origin + eyeposition; } void Sentient::SetBloodModel(Event *ev) { str name; str cache_name; str models_dir = "models/"; if (ev->NumArgs() < 1) { return; } blood_model = ev->GetString(1); cache_name = models_dir + blood_model; CacheResource(cache_name.c_str()); name = GetBloodSpurtName(); if (name.length()) { cache_name = models_dir + name; CacheResource(cache_name.c_str()); } name = GetBloodSplatName(); if (name.length()) { CacheResource(name.c_str()); } name = GetGibName(); if (name.length()) { cache_name = models_dir + name; CacheResource(cache_name.c_str()); } } void Sentient::AddItem(Item *object) { inventory.AddObject(object->entnum); } void Sentient::RemoveItem(Item *object) { if (!inventory.IndexOfObject(object->entnum)) { return; } inventory.RemoveObject(object->entnum); if (object->IsSubclassOfWeapon()) { DeactivateWeapon((Weapon *)object); } // // let the sent know about it // RemovedItem(object); } void Sentient::RemoveWeapons(void) { for (int i = inventory.NumObjects(); i > 0; i--) { int entnum = inventory.ObjectAt(i); Weapon *item = (Weapon *)G_GetEntity(entnum); if (item->IsSubclassOfWeapon()) { item->Delete(); } } } Weapon *Sentient::GetWeapon(int index) { for (int i = inventory.NumObjects(); i > 0; i--) { int entnum = inventory.ObjectAt(i); Weapon *item = (Weapon *)G_GetEntity(entnum); if (item->IsSubclassOfWeapon()) { if (!index) { return item; } index--; } } return NULL; } Item *Sentient::FindItemByExternalName(const char *itemname) { int num; int i; Item *item; num = inventory.NumObjects(); for (i = 1; i <= num; i++) { item = (Item *)G_GetEntity(inventory.ObjectAt(i)); assert(item); if (!Q_stricmp(item->getName(), itemname)) { return item; } } return NULL; } Item *Sentient::FindItemByModelname(const char *mdl) { int num; int i; Item *item; str tmpmdl; if (Q_stricmpn("models/", mdl, 7)) { tmpmdl = "models/"; } tmpmdl += mdl; num = inventory.NumObjects(); for (i = 1; i <= num; i++) { item = (Item *)G_GetEntity(inventory.ObjectAt(i)); assert(item); if (!Q_stricmp(item->model, tmpmdl)) { return item; } } return NULL; } Item *Sentient::FindItemByClassName(const char *classname) { int num; int i; Item *item; num = inventory.NumObjects(); for (i = 1; i <= num; i++) { item = (Item *)G_GetEntity(inventory.ObjectAt(i)); assert(item); if (!Q_stricmp(item->edict->entname, classname)) { return item; } } return NULL; } Item *Sentient::FindItem(const char *itemname) { Item *item; item = FindItemByExternalName(itemname); if (!item) { item = FindItemByModelname(itemname); if (!item) { item = FindItemByClassName(itemname); } } return item; } void Sentient::FreeInventory(void) { int num; int i; Item *item; Ammo *ammo; // Detach all Weapons DetachAllActiveWeapons(); // Delete all inventory items ( this includes weapons ) num = inventory.NumObjects(); for (i = num; i > 0; i--) { item = (Item *)G_GetEntity(inventory.ObjectAt(i)); delete item; } inventory.ClearObjectList(); // Remove all ammo num = ammo_inventory.NumObjects(); for (i = num; i > 0; i--) { ammo = (Ammo *)ammo_inventory.ObjectAt(i); delete ammo; } ammo_inventory.ClearObjectList(); if (IsSubclassOfPlayer()) { ((Player *)this)->InitMaxAmmo(); } } void Sentient::EventFreeInventory(Event *ev) { FreeInventory(); } qboolean Sentient::HasItem(const char *itemname) { return (FindItem(itemname) != NULL); } qboolean Sentient::HasWeaponClass(int iWeaponClass) { int i; Weapon *weapon; // look up for a weapon class for (i = 1; i <= inventory.NumObjects(); i++) { weapon = (Weapon *)G_GetEntity(inventory.ObjectAt(i)); if (weapon->IsSubclassOfWeapon()) { if (weapon->GetWeaponClass() & iWeaponClass) { // weapon class found return qtrue; } } } return qfalse; } qboolean Sentient::HasPrimaryWeapon(void) { int i; Weapon *weapon; // look up for a primary weapon for (i = 1; i <= inventory.NumObjects(); i++) { weapon = (Weapon *)G_GetEntity(inventory.ObjectAt(i)); if (weapon->IsSubclassOfWeapon()) { if (!(weapon->GetWeaponClass() & WEAPON_CLASS_MISC) && !weapon->IsSecondaryWeapon()) { // Sentient has a primary weapon return qtrue; } } } return qfalse; } qboolean Sentient::HasSecondaryWeapon(void) { int i; Weapon *weapon; // look up for a secondary weapon for (i = 1; i <= inventory.NumObjects(); i++) { weapon = (Weapon *)G_GetEntity(inventory.ObjectAt(i)); if (weapon->IsSubclassOfWeapon()) { if (weapon->IsSecondaryWeapon()) { // Sentient has a secondary weapon return qtrue; } } } return qfalse; } void Sentient::EventGiveTargetname(Event *ev) { int i; ConSimple *tlist; str name; const char *ptr; qboolean found; name = ev->GetString(1); ptr = name.c_str(); // skip over the $ ptr++; found = qfalse; str sName = ptr; tlist = world->GetTargetList(sName); for (i = 1; i <= tlist->NumObjects(); i++) { Entity *ent; ent = (Entity *)tlist->ObjectAt(i).Pointer(); assert(ent); if (ent->isSubclassOf(Item)) { Item *item; item = (Item *)ent; item->SetOwner(this); item->ProcessPendingEvents(); AddItem(item); found = qtrue; } } if (!found) { ScriptError("Could not give item with targetname %s to this sentient.\n", name.c_str()); } } Item *Sentient::giveItem(str itemname, int amount) { ClassDef *cls; Item *item; item = FindItem(itemname); if (item) { item->Add(amount); return item; } else { qboolean set_the_model = qfalse; // we don't have it, so lets try to resolve the item name // first lets see if it is a registered class name cls = getClass(itemname); if (!cls) { SpawnArgs args; // if that didn't work lets try to resolve it as a model args.setArg("model", itemname); cls = args.getClassDef(); if (!cls) { gi.DPrintf("No item called '%s'\n", itemname.c_str()); return NULL; } set_the_model = qtrue; } assert(cls); item = (Item *)cls->newInstance(); if (!item) { gi.DPrintf("Could not spawn an item called '%s'\n", itemname.c_str()); return NULL; } if (!item->isSubclassOf(Item)) { gi.DPrintf("Could not spawn an item called '%s'\n", itemname.c_str()); delete item; return NULL; } if (set_the_model) { // Set the model item->setModel(itemname); } item->SetOwner(this); item->ProcessPendingEvents(); item->setAmount(amount); AddItem(item); if (item->isSubclassOf(Weapon)) { // Post an event to give the ammo to the sentient Event *ev1; ev1 = new Event(EV_Weapon_GiveStartingAmmo); ev1->AddEntity(this); item->PostEvent(ev1, 0); } return item; } return NULL; } void Sentient::takeItem(const char *name) { Item *item; item = FindItem(name); if (item) { gi.DPrintf("Taking item %s away from player\n", item->getName().c_str()); item->PostEvent(EV_Remove, 0); return; } Ammo *ammo; ammo = FindAmmoByName(name); if (ammo) { gi.DPrintf("Taking ammo %s away from player\n", name); ammo->setAmount(0); } } void Sentient::takeAmmoType(const char *name) { Ammo *ammo; ammo = FindAmmoByName(name); if (ammo) { gi.DPrintf("Taking ammo %s away from player\n", name); ammo->setAmount(0); } } void Sentient::EventUseItem(Event *ev) { str name; weaponhand_t hand = WEAPON_MAIN; if (deadflag) { return; } name = ev->GetString(1); if (ev->NumArgs() > 1) { hand = WeaponHandNameToNum(ev->GetString(2)); } useWeapon(name, hand); } void Sentient::EventTake(Event *ev) { takeItem(ev->GetString(1)); } void Sentient::EventGiveItem(Event *ev) { str type; float amount; type = ev->GetString(1); if (ev->NumArgs() > 1) { amount = ev->GetInteger(2); } else { amount = 1; } giveItem(type, amount); } qboolean Sentient::DoGib(int meansofdeath, Entity *inflictor) { if (!com_blood->integer) { return false; } if ((meansofdeath == MOD_TELEFRAG) || (meansofdeath == MOD_LAVA)) { return true; } if (health > -75) { return false; } // Impact and Crush < -75 health if ((meansofdeath == MOD_IMPACT) || (meansofdeath == MOD_CRUSH)) { return true; } return false; } void Sentient::SpawnEffect(str modelname, Vector pos) { Animate *block; block = new Animate; block->setModel(modelname); block->setOrigin(pos); block->setSolidType(SOLID_NOT); block->setMoveType(MOVETYPE_NONE); block->NewAnim("idle"); block->PostEvent(EV_Remove, 1); } int Sentient::CheckHitLocation(int iLocation) { if (iLocation == 1) { if (WearingHelmet()) { return iLocation; } else { return HITLOC_HEAD; } } return iLocation; } #define WATER_CONVERSION_FACTOR 1.0f void Sentient::ArmorDamage(Event *ev) { Entity *inflictor; Sentient *attacker; float damage; Vector momentum; Vector position; Vector normal; Vector direction; Event *event; int dflags; int meansofdeath; int knockback; int location; //qboolean blocked; float damage_red; float damage_green; float damage_time; //qboolean set_means_of_death; static bool tmp = false; static cvar_t *AIDamageMult = NULL; if (!tmp) { tmp = true; AIDamageMult = gi.Cvar_Get("g_aiDamageMult", "1.0", 0); } if (IsDead()) { return; } attacker = (Sentient *)ev->GetEntity(1); damage = ev->GetFloat(2); inflictor = ev->GetEntity(3); position = ev->GetVector(4); direction = ev->GetVector(5); normal = ev->GetVector(6); knockback = ev->GetInteger(7); dflags = ev->GetInteger(8); meansofdeath = ev->GetInteger(9); location = CheckHitLocation(ev->GetInteger(10)); if (location == HITLOC_MISS) { return; } if ((takedamage == DAMAGE_NO) || (movetype == MOVETYPE_NOCLIP)) { return; } if ((!isClient() || g_gametype->integer != GT_SINGLE_PLAYER) && (location > HITLOC_GENERAL && location < NUMBODYLOCATIONS)) { damage *= m_fDamageMultipliers[location]; } else if (isClient() && attacker && attacker->IsSubclassOfActor() && g_gametype->integer == GT_SINGLE_PLAYER) { damage *= AIDamageMult->value; } // See if sentient is immune to this type of damage if (Immune(meansofdeath)) { /* means_of_death = meansofdeath; // Send pain event event = new Event( EV_Pain ); event->AddEntity( attacker ); event->AddFloat( 0 ); event->AddVector( position ); event->AddVector( direction ); event->AddVector( normal ); event->AddInteger( knockback ); event->AddInteger( dflags ); event->AddInteger( meansofdeath ); event->AddInteger( location ); ProcessEvent( event ); */ return; } // See if the damage is melee and high enough on actor /* if( deadflag ) { // Spawn a blood spurt if this model has one if( ShouldBleed( meansofdeath, true ) ) { AddBloodSpurt( direction ); if( ShouldGib( meansofdeath, damage ) ) ProcessEvent( EV_Sentient_SpawnBloodyGibs ); } means_of_death = meansofdeath; if( meansofdeath == MOD_FIRE ) TryLightOnFire( meansofdeath, attacker ); // Send pain event event = new Event( EV_Pain ); event->AddEntity( attacker ); event->AddFloat( damage ); event->AddVector( position ); event->AddVector( direction ); event->AddVector( normal ); event->AddInteger( knockback ); event->AddInteger( dflags ); event->AddInteger( meansofdeath ); event->AddInteger( location ); ProcessEvent( event ); return; } */ // Do the kick if (!(dflags & DAMAGE_NO_KNOCKBACK)) { if ((knockback) && (movetype != MOVETYPE_NONE) && (movetype != MOVETYPE_STATIONARY) && (movetype != MOVETYPE_BOUNCE) && (movetype != MOVETYPE_PUSH) && (movetype != MOVETYPE_STOP)) { float m; Event *immunity_event; if (mass < 20) { m = 20; } else { m = mass; } direction.normalize(); if (isClient() && (attacker == this) && deathmatch->integer) { momentum = direction * (1700.0f * (float)knockback / m); // the rocket jump hack... } else { momentum = direction * (500.0f * (float)knockback / m); } if (dflags & DAMAGE_BULLET) { // Clip the z velocity for bullet weapons if (momentum.z > 75) { momentum.z = 75; } } velocity += momentum; // Make this sentient vulnerable to falling damage now if (Immune(MOD_FALLING)) { immunity_event = new Event(EV_Entity_RemoveImmunity); immunity_event->AddString("falling"); ProcessEvent(immunity_event); } } } if (g_debugdamage->integer) { G_DebugDamage(damage, this, attacker, inflictor); } if (!(flags & FL_GODMODE) && ((g_gametype->integer != GT_SINGLE_PLAYER) || !(attacker) || (attacker) == this || !(attacker->IsSubclassOfSentient()) || (attacker->m_Team != m_Team))) { health -= damage; } // Set means of death means_of_death = meansofdeath; /* // Spawn a blood spurt if this model has one if( ShouldBleed( meansofdeath, false ) && !blocked ) { AddBloodSpurt( direction ); if( ( this->isSubclassOf( Actor ) || damage > 10 ) && ShouldGib( meansofdeath, damage ) ) ProcessEvent( EV_Sentient_SpawnBloodyGibs ); } */ if (health <= 0) { // See if we can kill this actor or not if (this->IsSubclassOfActor()) { Actor *act = (Actor *)this; if (act->IsImmortal()) { health = 1; } } } if (meansofdeath == MOD_SLIME) { damage_green = damage / 50; if (damage_green > 1.0f) damage_green = 1.0f; if ((damage_green < 0.2) && (damage_green > 0)) damage_green = 0.2f; damage_red = 0; } else { damage_red = damage / 50; if (damage_red > 1.0f) damage_red = 1.0f; if ((damage_red < 0.2) && (damage_red > 0)) damage_red = 0.2f; damage_green = 0; } damage_time = damage / 50; if (damage_time > 2) damage_time = 2; //SetOffsetColor(damage_red, damage_green, 0, damage_time); if (health < 0.1) { // Make sure health is now 0 health = 0; event = new Event(EV_Killed, 10); event->AddEntity(attacker); event->AddFloat(damage); event->AddEntity(inflictor); event->AddVector(position); event->AddVector(direction); event->AddVector(normal); event->AddInteger(knockback); event->AddInteger(dflags); event->AddInteger(meansofdeath); event->AddInteger(location); ProcessEvent(event); } if (health > 0) { // Send pain event event = new Event(EV_Pain, 10); event->AddEntity(attacker); event->AddFloat(damage); event->AddEntity(inflictor); event->AddVector(position); event->AddVector(direction); event->AddVector(normal); event->AddInteger(knockback); event->AddInteger(dflags); event->AddInteger(meansofdeath); event->AddInteger(location); ProcessEvent(event); } return; } qboolean Sentient::CanBlock(int meansofdeath, qboolean full_block) { // Check to see what a full block can't even block switch (meansofdeath) { case MOD_TELEFRAG: case MOD_SLIME: case MOD_LAVA: case MOD_FALLING: case MOD_IMPALE: case MOD_ON_FIRE: case MOD_ELECTRICWATER: return false; } // Full blocks block everything else if (full_block) { return true; } // Check to see what a small block can't block switch (meansofdeath) { case MOD_FIRE: case MOD_CRUSH_EVERY_FRAME: return false; } // Everything else is blocked return true; } void Sentient::AddBloodSpurt(Vector direction) { Entity *blood; Vector dir; Event *event; str blood_splat_name; float blood_splat_size; float length; trace_t trace; float scale; if (!com_blood->integer) { return; } next_bleed_time = level.time + .5; // Calculate a good scale for the blood if (mass < 50) { scale = .5; } else if (mass > 300) { scale = 1.5; } else if (mass >= 200) { scale = mass / 200.0; } else { scale = .5 + (mass - 50) / 300; } // Add blood spurt blood = new Animate; blood->setModel(blood_model); dir[0] = -direction[0]; dir[1] = -direction[1]; dir[2] = -direction[2]; blood->angles = dir.toAngles(); blood->setAngles(blood->angles); blood->setOrigin(centroid); blood->origin.copyTo(blood->edict->s.origin2); blood->setSolidType(SOLID_NOT); blood->setScale(scale); event = new Event(EV_Remove); blood->PostEvent(event, 1); // Add blood splats near feet blood_splat_name = GetBloodSplatName(); blood_splat_size = GetBloodSplatSize(); if (blood_splat_name.length() && G_Random() < 0.5) { dir = origin - centroid; dir.z -= 50; dir.x += G_CRandom(20); dir.y += G_CRandom(20); length = dir.length(); dir.normalize(); dir = dir * (length + 10); trace = G_Trace(centroid, vec_zero, vec_zero, centroid + dir, NULL, MASK_DEADSOLID, false, "AddBloodSpurt"); if (trace.fraction < 1) { Decal *decal = new Decal; decal->setShader(blood_splat_name); decal->setOrigin(Vector(trace.endpos) + (Vector(trace.plane.normal) * 0.2f)); decal->setDirection(trace.plane.normal); decal->setOrientation("random"); decal->setRadius(blood_splat_size + G_Random(blood_splat_size)); } } } qboolean Sentient::ShouldBleed(int meansofdeath, qboolean dead) { // Make sure we have a blood model if (!blood_model.length()) { return false; } // See if we can bleed now based on means of death switch (meansofdeath) { // Sometimes bleed (based on time) case MOD_BULLET: case MOD_CRUSH_EVERY_FRAME: case MOD_ELECTRICWATER: if (next_bleed_time > level.time) { return false; } break; // Sometimes bleed (based on chance) case MOD_SHOTGUN: if (G_Random() > 0.1) { return false; } break; // Never bleed case MOD_SLIME: case MOD_LAVA: case MOD_FIRE: case MOD_FLASHBANG: case MOD_ON_FIRE: case MOD_FALLING: return false; } // Always bleed by default return true; } // ShouldGib assumes that ShouldBleed has already been called qboolean Sentient::ShouldGib(int meansofdeath, float damage) { // See if we can gib based on means of death switch (meansofdeath) { // Always gib case MOD_CRUSH_EVERY_FRAME: return true; break; // Sometimes gib case MOD_BULLET: if (G_Random(100) < damage * 10) { return true; } break; case MOD_BEAM: if (G_Random(100) < damage * 5) { return true; } break; // Never gib case MOD_SLIME: case MOD_LAVA: case MOD_FIRE: case MOD_FLASHBANG: case MOD_ON_FIRE: case MOD_FALLING: case MOD_ELECTRICWATER: return false; } // Default is random based on how much damage done if (G_Random(100) < damage * 2) { return true; } return false; } str Sentient::GetBloodSpurtName(void) { str blood_spurt_name; if (blood_model == "fx_bspurt.tik") { blood_spurt_name = "fx_bspurt2.tik"; } else if (blood_model == "fx_gspurt.tik") { blood_spurt_name = "fx_gspurt2.tik"; } else if (blood_model == "fx_bspurt_blue.tik") { blood_spurt_name = "fx_bspurt2_blue.tik"; } return blood_spurt_name; } str Sentient::GetBloodSplatName(void) { str blood_splat_name; if (blood_model == "fx_bspurt.tik") { blood_splat_name = "bloodsplat.spr"; } else if (blood_model == "fx_gspurt.tik") { blood_splat_name = "greensplat.spr"; } else if (blood_model == "fx_bspurt_blue.tik") { blood_splat_name = "bluesplat.spr"; } return blood_splat_name; } float Sentient::GetBloodSplatSize(void) { float m; m = mass; if (m < 50) { m = 50; } else if (m > 250) { m = 250; } return (10 + (m - 50) / 200 * 6); } str Sentient::GetGibName(void) { str gib_name; if (blood_model == "fx_bspurt.tik") { gib_name = "fx_rgib"; } else if (blood_model == "fx_gspurt.tik") { gib_name = "fx_ggib"; } return gib_name; } int Sentient::NumInventoryItems(void) { return inventory.NumObjects(); } Item *Sentient::NextItem(Item *item) { Item *next_item; int i; int n; qboolean item_found = false; if (!item) { item_found = true; } else if (!inventory.ObjectInList(item->entnum)) { error("NextItem", "Item not in list"); } n = inventory.NumObjects(); for (i = 1; i <= n; i++) { next_item = (Item *)G_GetEntity(inventory.ObjectAt(i)); assert(next_item); if (next_item->isSubclassOf(InventoryItem) && item_found) { return next_item; } if (next_item == item) { item_found = true; } } return NULL; } Item *Sentient::PrevItem(Item *item) { Item *prev_item; int i; int n; qboolean item_found = false; if (!item) { item_found = true; } else if (!inventory.ObjectInList(item->entnum)) { error("NextItem", "Item not in list"); } n = inventory.NumObjects(); for (i = n; i >= 1; i--) { prev_item = (Item *)G_GetEntity(inventory.ObjectAt(i)); assert(prev_item); if (prev_item->isSubclassOf(InventoryItem) && item_found) { return prev_item; } if (prev_item == item) { item_found = true; } } return NULL; } void Sentient::DropInventoryItems(void) { int num; int i; Item *item; if (m_bForceDropHealth) { giveItem("ITEMS/item_25_healthbox.tik", 25); } else if (skill->integer != 2 && !level.mbNoDropHealth) { static cvar_t *ai_health_kar = gi.Cvar_Get("ai_health_kar", "6", CVAR_CHEAT); static cvar_t *ai_health_mp40 = gi.Cvar_Get("ai_health_mp40points", "2", CVAR_CHEAT); Weapon *weapon = GetActiveWeapon(WEAPON_MAIN); if (weapon) { if (!Q_stricmp("rifle", Director.GetString(weapon->GetWeaponGroup()))) { level.mHealthPopCount++; } else { level.mHealthPopCount += ai_health_mp40->integer; } if (level.mHealthPopCount >= ai_health_kar->integer) { giveItem("ITEMS/item_25_healthbox.tik", 25); level.mHealthPopCount -= ai_health_kar->integer; } } } // Drop any inventory items num = inventory.NumObjects(); for (i = num; i >= 1; i--) { item = (Item *)G_GetEntity(inventory.ObjectAt(i)); // Added in 2.30 // Force drop the item when specified if (m_bForceDropWeapon && item->IsSubclassOfWeapon()) { item->Drop(); continue; } if (!m_bDontDropWeapons && !level.mbNoDropWeapons) { item->Drop(); continue; } if (!item->IsSubclassOfWeapon()) { item->Drop(); continue; } item->Delete(); } } qboolean Sentient::PowerupActive(void) { if (poweruptype && this->client) { gi.SendServerCommand(edict - g_entities, "print \"You are already using a powerup\n\""); } return poweruptype; } void Sentient::setModel(const char *mdl) { // Rebind all active weapons DetachAllActiveWeapons(); Entity::setModel(mdl); AttachAllActiveWeapons(); } void Sentient::TurnOffShadow(Event *ev) { edict->s.renderfx &= ~RF_SHADOW; } void Sentient::TurnOnShadow(Event *ev) { edict->s.renderfx |= RF_SHADOW; } void Sentient::Archive(Archiver& arc) { int i; int num; Animate::Archive(arc); arc.ArchiveSafePointer(&m_pNextSquadMate); arc.ArchiveSafePointer(&m_pPrevSquadMate); inventory.Archive(arc); if (arc.Saving()) { num = ammo_inventory.NumObjects(); } else { ammo_inventory.ClearObjectList(); } arc.ArchiveInteger(&num); for (i = 1; i <= num; i++) { Ammo *ptr; if (arc.Loading()) { ptr = new Ammo; ammo_inventory.AddObject(ptr); } else { ptr = ammo_inventory.ObjectAt(i); } arc.ArchiveObject(ptr); } arc.ArchiveFloat(&LMRF); arc.ArchiveInteger(&poweruptype); arc.ArchiveInteger(&poweruptimer); arc.ArchiveVector(&offset_color); arc.ArchiveVector(&offset_delta); arc.ArchiveFloat(&charge_start_time); arc.ArchiveString(&blood_model); for (i = 0; i < MAX_ACTIVE_WEAPONS; i++) { arc.ArchiveSafePointer(&activeWeaponList[i]); } newActiveWeapon.Archive(arc); arc.ArchiveSafePointer(&holsteredWeapon); arc.ArchiveBool(&weapons_holstered_by_code); lastActiveWeapon.Archive(arc); for (int i = 0; i < MAX_DAMAGE_MULTIPLIERS; i++) { arc.ArchiveFloat(&m_fDamageMultipliers[i]); } arc.ArchiveSafePointer(&m_pVehicle); arc.ArchiveSafePointer(&m_pTurret); arc.ArchiveSafePointer(&m_pLadder); arc.ArchiveString(&m_sHelmetSurface1); arc.ArchiveString(&m_sHelmetSurface2); arc.ArchiveString(&m_sHelmetTiki); arc.ArchiveFloat(&m_fHelmetSpeed); arc.ArchiveVector(&gunoffset); arc.ArchiveVector(&eyeposition); arc.ArchiveInteger(&viewheight); arc.ArchiveVector(&m_vViewVariation); arc.ArchiveInteger(&means_of_death); arc.ArchiveBool(&in_melee_attack); arc.ArchiveBool(&in_block); arc.ArchiveBool(&in_stun); arc.ArchiveBool(&on_fire); arc.ArchiveFloat(&on_fire_stop_time); arc.ArchiveFloat(&next_catch_on_fire_time); arc.ArchiveInteger(&on_fire_tagnums[0]); arc.ArchiveInteger(&on_fire_tagnums[1]); arc.ArchiveInteger(&on_fire_tagnums[2]); arc.ArchiveSafePointer(&fire_owner); arc.ArchiveBool(&attack_blocked); arc.ArchiveFloat(&attack_blocked_time); arc.ArchiveFloat(&max_mouth_angle); arc.ArchiveInteger(&max_gibs); arc.ArchiveFloat(&next_bleed_time); arc.ArchiveBool(&m_bFootOnGround_Right); arc.ArchiveBool(&m_bFootOnGround_Left); arc.ArchiveObjectPointer((Class **)&m_NextSentient); arc.ArchiveObjectPointer((Class **)&m_PrevSentient); arc.ArchiveVector(&mTargetPos); arc.ArchiveFloat(&mAccuracy); arc.ArchiveInteger(&m_Team); arc.ArchiveInteger(&m_iAttackerCount); arc.ArchiveSafePointer(&m_pLastAttacker); arc.ArchiveSafePointer(&m_Enemy); arc.ArchiveFloat(&m_fPlayerSightLevel); arc.ArchiveBool(&m_bIsDisguised); arc.ArchiveBool(&m_bHasDisguise); arc.ArchiveInteger(&m_ShowPapersTime); arc.ArchiveInteger(&m_iLastHitTime); arc.ArchiveInteger(&m_iThreatBias); arc.ArchiveBool(&m_bDontDropWeapons); arc.ArchiveBool(&m_bIsAnimal); arc.ArchiveBool(&m_bForceDropHealth); arc.ArchiveBool(&m_bForceDropWeapon); if (arc.Loading()) { if (WeaponsOut()) { Holster(true); } } // // Added in OPM // arc.ArchiveInteger(&iNextLandTime); } static bool IsItemName(const char *name) { if (!str::icmp(name, "models/items/camera.tik")) { return true; } else if (!str::icmp(name, "models/items/binoculars.tik")) { return true; } else if (!str::icmp(name, "models/items/papers.tik")) { return true; } else if (!str::icmp(name, "models/items/papers2.tik")) { return true; } return false; } void Sentient::ArchivePersistantData(Archiver& arc) { int i; int num; str name; int amount; Item *item; Entity *ent; // archive the inventory if (arc.Saving()) { // remove all special items before persistence for (i = inventory.NumObjects(); i > 0; i--) { int index; index = inventory.ObjectAt(i); ent = G_GetEntity(index); name = ent->model; if (IsItemName(name)) { ent->Delete(); } } // count up the total number num = inventory.NumObjects(); } else { inventory.ClearObjectList(); } // archive the number arc.ArchiveInteger(&num); // archive each item for (i = 1; i <= num; i++) { if (arc.Saving()) { Entity *ent; ent = G_GetEntity(inventory.ObjectAt(i)); if (ent && ent->isSubclassOf(Item)) { item = (Item *)ent; name = item->model; amount = item->getAmount(); } else { error("ArchivePersistantData", "Non Item in inventory\n"); } } arc.ArchiveString(&name); arc.ArchiveInteger(&amount); if (arc.Loading()) { item = giveItem(name, amount); } if (item && item->IsSubclassOfWeapon()) { Weapon *pWeap = static_cast(item); item->CancelEventsOfType(EV_Weapon_GiveStartingAmmo); if (arc.Saving()) { amount = pWeap->ClipAmmo(FIRE_PRIMARY); } arc.ArchiveInteger(&amount); if (arc.Loading()) { pWeap->SetAmmoAmount(amount, FIRE_PRIMARY); } } } // archive the ammo inventory if (arc.Saving()) { // count up the total number num = ammo_inventory.NumObjects(); } else { ammo_inventory.ClearObjectList(); } // archive the number arc.ArchiveInteger(&num); // archive each item for (i = 1; i <= num; i++) { str name; int amount; int maxamount; Ammo *ptr; if (arc.Saving()) { ptr = ammo_inventory.ObjectAt(i); name = ptr->getName(); amount = ptr->getAmount(); maxamount = ptr->getMaxAmount(); } arc.ArchiveString(&name); arc.ArchiveInteger(&amount); arc.ArchiveInteger(&maxamount); if (arc.Loading()) { GiveAmmo(name, amount, maxamount); } } for (i = 0; i < MAX_ACTIVE_WEAPONS; i++) { if (arc.Saving()) { if (activeWeaponList[i]) { name = activeWeaponList[i]->getName(); } else { name = "none"; } } arc.ArchiveString(&name); if (arc.Loading() && name != "none") { Weapon *weapon; weapon = (Weapon *)FindItem(name); if (weapon) { ChangeWeapon(weapon, (weaponhand_t)i); } } } if (GetActiveWeapon(WEAPON_MAIN)) { edict->s.eFlags &= ~EF_UNARMED; } else { edict->s.eFlags |= EF_UNARMED; } arc.ArchiveFloat(&health); arc.ArchiveFloat(&max_health); } void Sentient::DoubleArmor(void) { int i, n; n = inventory.NumObjects(); for (i = 1; i <= n; i++) { Item *item; item = (Item *)G_GetEntity(inventory.ObjectAt(i)); if (item->isSubclassOf(Armor)) { item->setAmount(item->getAmount() * 2); } } } void Sentient::JumpXY(Event *ev) { float forwardmove; float sidemove; float distance; float time; float speed; Vector yaw_forward; Vector yaw_left; forwardmove = ev->GetFloat(1); sidemove = ev->GetFloat(2); speed = ev->GetFloat(3); Vector(0, angles.y, 0).AngleVectors(&yaw_forward, &yaw_left); velocity = yaw_forward * forwardmove - yaw_left * sidemove; distance = velocity.length(); velocity *= speed / distance; time = distance / speed; velocity[2] = sv_gravity->integer * time * 0.5f; } void Sentient::BlockStart(Event *ev) { in_block = true; } void Sentient::BlockEnd(Event *ev) { in_block = false; } void Sentient::StunStart(Event *ev) { in_stun = true; } void Sentient::StunEnd(Event *ev) { in_stun = false; } void Sentient::ListInventory(void) { int i, count; // Display normal inventory count = inventory.NumObjects(); gi.Printf("'Name' : 'Amount'\n"); for (i = 1; i <= count; i++) { int entnum = inventory.ObjectAt(i); Item *item = (Item *)G_GetEntity(entnum); gi.Printf("'%s' : '%d'\n", item->getName().c_str(), item->getAmount()); } // Display ammo inventory count = ammo_inventory.NumObjects(); for (i = 1; i <= count; i++) { Ammo *ammo = ammo_inventory.ObjectAt(i); gi.Printf("'%s' : '%d'\n", ammo->getName().c_str(), ammo->getAmount()); } } void Sentient::SetAttackBlocked(bool blocked) { attack_blocked = blocked; attack_blocked_time = level.time; } void Sentient::SetMaxMouthAngle(Event *ev) { max_mouth_angle = ev->GetFloat(1); } void Sentient::TryLightOnFire(int meansofdeath, Entity *attacker) { gi.Printf("Sentient::TryLightOnFire not implemented. Needs fixed"); } void Sentient::OnFire(Event *ev) { gi.Printf("Sentient::OnFire not implemented. Needs fixed"); } void Sentient::StopOnFire(Event *ev) { gi.Printf("Sentient::StopOnFire not implemented. Needs fixed"); } void Sentient::SpawnBloodyGibs(Event *ev) { str gib_name; int number_of_gibs; float scale; Animate *ent; str real_gib_name; if (!com_blood->integer) { return; } //if ( GetActorFlag( ACTOR_FLAG_FADING_OUT ) ) // return; gib_name = GetGibName(); if (!gib_name.length()) { return; } // Determine the number of gibs to spawn if (ev->NumArgs() > 0) { number_of_gibs = ev->GetInteger(1); } else { if (max_gibs == 0) { return; } if (deadflag) { number_of_gibs = G_Random(max_gibs / 2) + 1; } else { number_of_gibs = G_Random(max_gibs) + 1; } } // Make sure we don't have too few or too many gibs if (number_of_gibs <= 0 || number_of_gibs > 9) { return; } if (ev->NumArgs() > 1) { scale = ev->GetFloat(2); } else { // Calculate a good scale value if (mass <= 50) { scale = 1.0f; } else if (mass <= 100) { scale = 1.1f; } else if (mass <= 250) { scale = 1.2f; } else { scale = 1.3f; } } // Spawn the gibs real_gib_name = gib_name; real_gib_name += number_of_gibs; real_gib_name += ".tik"; ent = new Animate; ent->setModel(real_gib_name.c_str()); ent->setScale(scale); ent->setOrigin(centroid); ent->NewAnim("idle"); ent->PostEvent(EV_Remove, 1); Sound("snd_decap", CHAN_BODY, 1, 300); } void Sentient::SetMaxGibs(Event *ev) { max_gibs = ev->GetInteger(1); } void Sentient::GetStateAnims(Container *c) {} void Sentient::CheckAnimations(Event *ev) { int i, j; Container co; const char *cs; GetStateAnims(&co); gi.DPrintf("Unused Animations in TIKI\n"); gi.DPrintf("-------------------------\n"); for (i = 0; i < NumAnims(); i++) { const char *c; c = gi.Anim_NameForNum(edict->tiki, i); for (j = 1; j <= co.NumObjects(); j++) { cs = co.ObjectAt(j); if (!Q_stricmp(c, cs)) { goto out; } else if (!Q_stricmpn(c, cs, strlen(cs))) // partial match { size_t state_len = strlen(cs); // Animation in tik file is longer than the state machine's anim if (strlen(c) > state_len) { if (c[state_len] != '_') // If next character is an '_' then no match { goto out; } } else { goto out; } } } // No match made gi.DPrintf("%s used in TIK file but not statefile\n", c); out:; } gi.DPrintf("Unknown Animations in Statefile\n"); gi.DPrintf("-------------------------------\n"); for (j = 1; j <= co.NumObjects(); j++) { if (!HasAnim(co.ObjectAt(j))) { gi.DPrintf("%s in statefile is not in TIKI\n", co.ObjectAt(j)); } } } void Sentient::EventGerman(Event *ev) { bool bRejoinSquads = false; if (ev->IsFromScript()) { if (m_Team) { bRejoinSquads = true; } } if (bRejoinSquads) { ClearEnemies(); DisbandSquadMate(this); } Unlink(); m_Team = TEAM_GERMAN; Link(); if (bRejoinSquads) { JoinNearbySquads(1024.0f); } // Added in 2.0 // Tell clients about sentient team edict->s.eFlags &= ~EF_ALLIES; edict->s.eFlags |= EF_AXIS; } void Sentient::EventAmerican(Event *ev) { bool bRejoinSquads = false; if (ev->IsFromScript()) { if (m_Team != TEAM_AMERICAN) { bRejoinSquads = true; } } if (bRejoinSquads) { ClearEnemies(); DisbandSquadMate(this); } Unlink(); m_Team = TEAM_AMERICAN; Link(); if (bRejoinSquads) { JoinNearbySquads(1024); } if (IsSubclassOfActor()) { Actor *pActor = static_cast(this); pActor->m_csMood = STRING_NERVOUS; pActor->m_csIdleMood = STRING_NERVOUS; } // Added in 2.0 // Tell clients about sentient team edict->s.eFlags &= ~EF_AXIS; edict->s.eFlags |= EF_ALLIES; } void Sentient::EventGetTeam(Event *ev) { if (m_Team == TEAM_AMERICAN) { ev->AddConstString(STRING_AMERICAN); } else if (m_Team == TEAM_GERMAN) { ev->AddConstString(STRING_GERMAN); } else { ev->AddConstString(STRING_EMPTY); } } void Sentient::ClearEnemies() {} void Sentient::EventGetThreatBias(Event *ev) { ev->AddInteger(m_iThreatBias); } void Sentient::EventSetThreatBias(Event *ev) { str sBias; if (ev->IsStringAt(1)) { sBias = ev->GetString(1); if (!Q_stricmp(sBias, "ignoreme")) { m_iThreatBias = THREATBIAS_IGNOREME; return; } } m_iThreatBias = ev->GetInteger(1); } void Sentient::SetDamageMult(Event *ev) { int index = ev->GetInteger(1); if (index < 0 || index >= MAX_DAMAGE_MULTIPLIERS) { ScriptError("Index must be between 0-" STRING(MAX_DAMAGE_MULTIPLIERS - 1) "."); } m_fDamageMultipliers[index] = ev->GetFloat(2); } void Sentient::SetupHelmet(str sHelmetTiki, float fSpeed, float fDamageMult, str sHelmetSurface1, str sHelmetSurface2) { m_sHelmetTiki = sHelmetTiki; m_sHelmetSurface1 = sHelmetSurface1; m_sHelmetSurface2 = sHelmetSurface2; m_fHelmetSpeed = fSpeed; m_fDamageMultipliers[1] = fDamageMult; } void Sentient::EventSetupHelmet(Event *ev) { str sHelmetTiki; str sHelmetSurface; sHelmetTiki = ev->GetString(1); sHelmetSurface = ev->GetString(4); if (ev->NumArgs() == 4) { SetupHelmet(sHelmetTiki, ev->GetFloat(2), ev->GetFloat(3), sHelmetSurface, sHelmetSurface); } else { SetupHelmet(sHelmetTiki, ev->GetFloat(2), ev->GetFloat(3), sHelmetSurface, ev->GetString(5)); } } bool Sentient::WearingHelmet(void) { if (!m_sHelmetSurface1.length()) { return false; } int iSurf = gi.Surface_NameToNum(edict->tiki, m_sHelmetSurface1); if (iSurf >= 0) { return (~edict->s.surfaces[iSurf] & MDL_SURFACE_NODRAW) != 0; } else { return false; } } void Sentient::EventPopHelmet(Event *ev) { int iSurf; vec3_t vWorldAngles; vec3_t vXAxis, vYAxis, vZAxis; orientation_t oLocalTag, oWorldTag; int iHeadTag; float fRandom; float fPitchVelocity, fYawVelocity; HelmetObject *obj; if (!WearingHelmet()) { return; } iSurf = gi.Surface_NameToNum(edict->tiki, m_sHelmetSurface1.c_str()); // Hide the helmet edict->s.surfaces[iSurf] |= MDL_SURFACE_NODRAW; if (m_sHelmetSurface2.length()) { iSurf = gi.Surface_NameToNum(edict->tiki, m_sHelmetSurface2.c_str()); if (iSurf >= 0) { // Hide the second helmet edict->s.surfaces[iSurf] |= MDL_SURFACE_NODRAW; } else { Com_Printf( "Warning: Surface %s found, but %s not found in setting up helmet for %s.\n", m_sHelmetSurface1.c_str(), m_sHelmetSurface2.c_str(), edict->tiki->name ); } } if (!m_sHelmetTiki.length()) { return; } iHeadTag = gi.Tag_NumForName(edict->tiki, "Bip01 Head"); oLocalTag = G_TIKI_Orientation(edict, iHeadTag); for (int i = 0; i < 3; i++) { vXAxis[i] = oLocalTag.axis[0][i]; vYAxis[i] = oLocalTag.axis[1][i]; vZAxis[i] = oLocalTag.axis[2][i]; } for (int i = 0; i < 3; i++) { oLocalTag.axis[0][i] = -vYAxis[i]; oLocalTag.axis[1][i] = -vZAxis[i]; oLocalTag.axis[2][i] = vXAxis[i]; } VectorCopy(origin, oWorldTag.origin); for (int i = 0; i < 3; i++) { VectorMA(oWorldTag.origin, oLocalTag.origin[i], orientation[i], oWorldTag.origin); } MatrixMultiply(oLocalTag.axis, orientation, oWorldTag.axis); MatrixToEulerAngles(oWorldTag.axis, vWorldAngles); obj = new HelmetObject(); obj->setOrigin(oWorldTag.origin); obj->setAngles(vWorldAngles); obj->setModel(m_sHelmetTiki); fRandom = crandom() * 30; VectorScale(obj->velocity, fRandom, oWorldTag.axis[0]); fRandom = crandom() * 30; VectorMA(obj->velocity, fRandom, oWorldTag.axis[1], obj->velocity); fRandom = (crandom() * 0.3f + 1.0f) * m_fHelmetSpeed; VectorMA(obj->velocity, fRandom, oWorldTag.axis[2], obj->velocity); fPitchVelocity = crandom() * 300; fYawVelocity = crandom() * 400; obj->avelocity.x = fPitchVelocity; obj->avelocity.y = fYawVelocity; obj->avelocity.z = crandom() * 300.0; } void Sentient::ReceivedItem(Item *item) {} void Sentient::RemovedItem(Item *item) {} void Sentient::AssertValidSquad() { for (Sentient *pSquadMate = this; pSquadMate != this; pSquadMate = pSquadMate->m_pNextSquadMate) {} } bool Sentient::IsTeamMate(Sentient *pOther) { return (pOther->m_bIsDisguised || pOther->m_Team == m_Team); } void Sentient::JoinNearbySquads(float fJoinRadius) { float fJoinRadiusSquared = Square(fJoinRadius); for (Sentient *pFriendly = level.m_HeadSentient[m_Team]; pFriendly != NULL; pFriendly = pFriendly->m_NextSentient) { if (pFriendly->IsDead() || IsSquadMate(pFriendly) || pFriendly->m_Team != m_Team) { continue; } if (fJoinRadius >= Vector::DistanceSquared(pFriendly->origin, origin)) { MergeWithSquad(pFriendly); } } } void Sentient::MergeWithSquad(Sentient *pFriendly) { Sentient *pFriendNext; Sentient *pSelfPrev; if (!pFriendly || IsDead() || pFriendly->IsDead()) { return; } pFriendNext = pFriendly->m_pNextSquadMate; pSelfPrev = m_pPrevSquadMate; pFriendly->m_pNextSquadMate = this; m_pPrevSquadMate = pFriendly; pFriendNext->m_pPrevSquadMate = pSelfPrev; pSelfPrev->m_pNextSquadMate = pFriendNext; } void Sentient::DisbandSquadMate(Sentient *pExFriendly) { Sentient *pPrev; Sentient *pNext; AssertValidSquad(); pPrev = pExFriendly->m_pPrevSquadMate; pNext = pExFriendly->m_pNextSquadMate; pPrev->m_pNextSquadMate = pNext; pNext->m_pPrevSquadMate = pPrev; pExFriendly->m_pPrevSquadMate = pExFriendly; pExFriendly->m_pNextSquadMate = pExFriendly; AssertValidSquad(); pNext->AssertValidSquad(); } bool Sentient::IsSquadMate(Sentient *pFriendly) { Sentient *pSquadMate = this; while (1) { if (pSquadMate == pFriendly) { return true; } pSquadMate = pSquadMate->m_pNextSquadMate; if (pSquadMate == this) { return false; } } } bool Sentient::IsDisabled() const { return false; } VehicleTank *Sentient::GetVehicleTank(void) { if (m_pVehicle && m_pVehicle->IsSubclassOfVehicleTank()) { return (VehicleTank *)m_pVehicle.Pointer(); } else { return NULL; } } void Sentient::UpdateFootsteps(void) { int iAnimNum; int iAnimFlags; int iTagNum; iAnimFlags = 0; for (iAnimNum = 0; iAnimNum < MAX_FRAMEINFOS; iAnimNum++) { if (edict->s.frameInfo[iAnimNum].weight != 0 && CurrentAnim(iAnimNum) >= 0) { iAnimFlags |= gi.Anim_Flags(edict->tiki, CurrentAnim(iAnimNum)); } } if (!(iAnimFlags & TAF_AUTOSTEPS_RUNNING) || !(iAnimFlags & TAF_AUTOSTEPS)) { // if walking, or if the animation doesn't step m_bFootOnGround_Right = true; m_bFootOnGround_Left = true; return; } if (m_bFootOnGround_Right) { iTagNum = gi.Tag_NumForName(edict->tiki, "Bip01 R Foot"); if (iTagNum >= 0) { m_bFootOnGround_Right = G_TIKI_IsOnGround(edict, iTagNum, 13.653847f); } else { m_bFootOnGround_Right = true; } } else { iTagNum = gi.Tag_NumForName(edict->tiki, "Bip01 R Foot"); if (iTagNum >= 0) { if (G_TIKI_IsOnGround(edict, iTagNum, 13.461539f)) { BroadcastAIEvent(10, G_AIEventRadius(10)); // simulate footstep sounds Footstep("Bip01 L Foot", (iAnimFlags & TAF_AUTOSTEPS_RUNNING), (iAnimFlags & TAF_AUTOSTEPS_EQUIPMENT)); m_bFootOnGround_Right = true; } } else { m_bFootOnGround_Right = true; } } if (m_bFootOnGround_Left) { iTagNum = gi.Tag_NumForName(edict->tiki, "Bip01 L Foot"); if (iTagNum >= 0) { m_bFootOnGround_Left = G_TIKI_IsOnGround(edict, iTagNum, 13.653847f); } else { m_bFootOnGround_Left = true; } } else { iTagNum = gi.Tag_NumForName(edict->tiki, "Bip01 L Foot"); if (iTagNum >= 0) { if (G_TIKI_IsOnGround(edict, iTagNum, 13.461539f)) { BroadcastAIEvent(10, G_AIEventRadius(10)); // simulate footstep sounds Footstep("Bip01 R Foot", (iAnimFlags & TAF_AUTOSTEPS_RUNNING), (iAnimFlags & TAF_AUTOSTEPS_EQUIPMENT)); m_bFootOnGround_Left = true; } } else { m_bFootOnGround_Left = true; } } } qboolean Sentient::AIDontFace() const { return qfalse; } void Sentient::EventDropItems(Event *ev) { DropInventoryItems(); } void Sentient::EventDontDropWeapons(Event *ev) { if (ev->NumArgs() > 0) { m_bDontDropWeapons = ev->GetBoolean(1); } else { m_bDontDropWeapons = true; } } void Sentient::EventForceDropWeapon(Event *ev) { if (ev->NumArgs() > 0) { m_bForceDropWeapon = ev->GetBoolean(1); } else { m_bForceDropWeapon = true; } } void Sentient::EventForceDropHealth(Event *ev) { if (ev->NumArgs() > 0) { m_bForceDropHealth = ev->GetBoolean(1); } else { m_bForceDropHealth = true; } } void Sentient::EventGetForceDropWeapon(Event *ev) { ev->AddInteger(m_bForceDropWeapon); } void Sentient::EventGetForceDropHealth(Event *ev) { ev->AddInteger(m_bForceDropHealth); } void Sentient::SetViewAngles(Vector angles) {} void Sentient::SetTargetViewAngles(Vector angles) {} Vector Sentient::GetViewAngles(void) { return angles; } void Sentient::AddViewVariation(const Vector& vVariation) { m_vViewVariation += vVariation; } void Sentient::SetMinViewVariation(const Vector& vVariation) { m_vViewVariation.x = Q_min(m_vViewVariation.x, vVariation.x); m_vViewVariation.y = Q_min(m_vViewVariation.y, vVariation.y); m_vViewVariation.z = Q_min(m_vViewVariation.z, vVariation.z); } void Sentient::SetHolsteredByCode(bool holstered) { weapons_holstered_by_code = holstered; } Vehicle *Sentient::GetVehicle() const { return m_pVehicle; } void Sentient::SetVehicle(Vehicle *pVehicle) { m_pVehicle = NULL; } TurretGun *Sentient::GetTurret() const { return m_pTurret; } void Sentient::SetTurret(TurretGun *pTurret) { m_pTurret = pTurret; } #define GROUND_DISTANCE 8 #define WATER_NO_SPLASH_HEIGHT 16 void Sentient::EventClientLanding(Event *ev) { float fVolume = ev->NumArgs() >= 1 ? ev->GetFloat(1) : 1; int iEquipment = ev->NumArgs() >= 2 ? ev->GetInteger(2) : 1; LandingSound(fVolume, iEquipment); } void Sentient::FootstepMain(trace_t *trace, int iRunning, int iEquipment) { int contents; int surftype; float fVolume; vec3_t vPos; vec3_t midlegs; str sSoundName; VectorCopy(trace->endpos, vPos); sSoundName = "snd_step_"; contents = gi.pointcontents(trace->endpos, -1); if (contents & MASK_WATER) { // take our ground position and trace upwards VectorCopy(trace->endpos, midlegs); midlegs[2] += WATER_NO_SPLASH_HEIGHT; contents = gi.pointcontents(midlegs, -1); if (contents & MASK_WATER) { sSoundName += "wade"; } else { sSoundName += "puddle"; } } else { surftype = trace->surfaceFlags & MASK_SURF_TYPE; switch (surftype) { case SURF_FOLIAGE: sSoundName += "foliage"; break; case SURF_SNOW: sSoundName += "snow"; break; case SURF_CARPET: sSoundName += "carpet"; break; case SURF_SAND: sSoundName += "sand"; break; case SURF_PUDDLE: sSoundName += "puddle"; break; case SURF_GLASS: sSoundName += "glass"; break; case SURF_GRAVEL: sSoundName += "gravel"; break; case SURF_MUD: sSoundName += "mud"; break; case SURF_DIRT: sSoundName += "dirt"; break; case SURF_GRILL: sSoundName += "grill"; break; case SURF_GRASS: sSoundName += "grass"; break; case SURF_ROCK: sSoundName += "stone"; break; case SURF_PAPER: sSoundName += "paper"; break; case SURF_WOOD: sSoundName += "wood"; break; case SURF_METAL: sSoundName += "metal"; break; default: sSoundName += "stone"; break; } } if (iRunning) { if (iRunning == -1) { fVolume = 0.5; } else { fVolume = 1.0; } } else { fVolume = 0.25; } if (!iRunning && g_gametype->integer == GT_SINGLE_PLAYER) { return; } PlayNonPvsSound(sSoundName, fVolume); if (iEquipment && random() < 0.3) { // also play equipment sound PlayNonPvsSound("snd_step_equipment", fVolume); } } void Sentient::Footstep(const char *szTagName, int iRunning, int iEquipment) { int i; int iTagNum; vec3_t vStart, vEnd; vec3_t midlegs; vec3_t vMins, vMaxs; str sSoundName; trace_t trace; orientation_t oTag; // send a trace down from the player to the ground VectorCopy(this->origin, vStart); vStart[2] += GROUND_DISTANCE; if (szTagName) { iTagNum = gi.Tag_NumForName(this->edict->tiki, szTagName); if (iTagNum != -1) { oTag = G_TIKI_Orientation(this->edict, iTagNum); for (i = 0; i < 2; i++) { VectorMA(vStart, oTag.origin[i], this->orientation[i], vStart); } } } if (iRunning == -1) { AngleVectors(this->angles, midlegs, NULL, NULL); VectorMA(vStart, -16, midlegs, vStart); VectorMA(vStart, 64, midlegs, vEnd); VectorSet(vMins, -2, -2, -8); VectorSet(vMaxs, 2, 2, 8); } else { VectorSet(vMins, -4, -4, 0); VectorSet(vMaxs, 4, 4, 2); // add 16 units above feets vStart[2] += 16.0; VectorCopy(vStart, vEnd); vEnd[2] -= 64.0; } if (IsSubclassOfPlayer()) { trace = G_Trace(vStart, vMins, vMaxs, vEnd, edict, MASK_PLAYERSOLID, qtrue, "Player Footsteps"); } else { trace = G_Trace(vStart, vMins, vMaxs, vEnd, edict, MASK_MONSTERSOLID, qfalse, "Monster Footsteps"); } if (trace.fraction == 1.0f) { return; } FootstepMain(&trace, iRunning, iEquipment); } void Sentient::LandingSound(float volume, int iEquipment) { int contents; int surftype; vec3_t vStart, vEnd; vec3_t midlegs; str sSoundName; trace_t trace; static vec3_t g_vFootstepMins = {-4, -4, 0}; static vec3_t g_vFootstepMaxs = {4, 4, 2}; if (this->iNextLandTime > level.inttime) { this->iNextLandTime = level.inttime + 200; return; } this->iNextLandTime = level.time + 200; VectorCopy(this->origin, vStart); vStart[2] += GROUND_DISTANCE; VectorCopy(vStart, vEnd); vEnd[2] -= 64.0; if (IsSubclassOfPlayer()) { trace = G_Trace(vStart, g_vFootstepMins, g_vFootstepMaxs, vEnd, edict, MASK_PLAYERSOLID, qtrue, "Player Footsteps"); } else { trace = G_Trace( vStart, g_vFootstepMins, g_vFootstepMaxs, vEnd, edict, MASK_MONSTERSOLID, qfalse, "Monster Footsteps" ); } if (trace.fraction == 1.0) { return; } sSoundName += "snd_landing_"; contents = gi.pointcontents(trace.endpos, -1); if (contents & MASK_WATER) { // take our ground position and trace upwards VectorCopy(trace.endpos, midlegs); midlegs[2] += WATER_NO_SPLASH_HEIGHT; contents = gi.pointcontents(midlegs, -1); if (contents & MASK_WATER) { sSoundName += "wade"; } else { sSoundName += "puddle"; } } else { surftype = trace.surfaceFlags & MASK_SURF_TYPE; switch (surftype) { case SURF_FOLIAGE: sSoundName += "foliage"; break; case SURF_SNOW: sSoundName += "snow"; break; case SURF_CARPET: sSoundName += "carpet"; break; case SURF_SAND: sSoundName += "sand"; break; case SURF_PUDDLE: sSoundName += "puddle"; break; case SURF_GLASS: sSoundName += "glass"; break; case SURF_GRAVEL: sSoundName += "gravel"; break; case SURF_MUD: sSoundName += "mud"; break; case SURF_DIRT: sSoundName += "dirt"; break; case SURF_GRILL: sSoundName += "grill"; break; case SURF_GRASS: sSoundName += "grass"; break; case SURF_ROCK: sSoundName += "stone"; break; case SURF_PAPER: sSoundName += "paper"; break; case SURF_WOOD: sSoundName += "wood"; break; case SURF_METAL: sSoundName += "metal"; break; default: sSoundName += "stone"; break; } } PlayNonPvsSound(sSoundName, volume); if (iEquipment && random() < 0.5) { PlayNonPvsSound("snd_step_equipment", volume); } }