diff --git a/src/client/entry.qc b/src/client/entry.qc index ea906abd..30a3c026 100644 --- a/src/client/entry.qc +++ b/src/client/entry.qc @@ -20,7 +20,6 @@ void CSQC_Init(float apilevel, string enginename, float engineversion) { - Sound_Init(); pSeat = &g_seats[0]; pSeatLocal = &g_seatslocal[0]; @@ -86,6 +85,10 @@ CSQC_Init(float apilevel, string enginename, float engineversion) registercommand("+zoomin"); registercommand("-zoomin"); + /* Sound shaders */ + Sound_Init(); + PropData_Init(); + /* VOX */ Vox_Init(); @@ -1107,6 +1110,7 @@ CSQC_Shutdown(void) Sentences_Shutdown(); Titles_Shutdown(); Sound_Shutdown(); + PropData_Shutdown(); Vox_Shutdown(); EFX_Shutdown(); } diff --git a/src/client/modelevent.qc b/src/client/modelevent.qc index c99631b2..4a7e4b17 100644 --- a/src/client/modelevent.qc +++ b/src/client/modelevent.qc @@ -27,6 +27,9 @@ void Event_ProcessModel(float flTimeStamp, int iCode, string strData) { switch(iCode) { + case 1004: + sound(self, CHAN_BODY, strData, 1.0f, ATTN_NORM); + break; case 5004: /* view model sound */ localsound(strData, CHAN_AUTO, 1.0); break; diff --git a/src/gs-entbase/server/func_breakable.qc b/src/gs-entbase/server/func_breakable.qc index cc6b7685..9cdcc63c 100644 --- a/src/gs-entbase/server/func_breakable.qc +++ b/src/gs-entbase/server/func_breakable.qc @@ -108,6 +108,7 @@ class func_breakable:CBaseTrigger int m_iMaterial; float m_flDelay; float m_flExplodeMag; + float m_flExplodeRad; string m_strBreakSpawn; /*entity m_pressAttacker; @@ -170,7 +171,7 @@ func_breakable::Explode(void) vector rp = absmin + (0.5 * (absmax - absmin)); FX_BreakModel(vlen(size) / 10, absmin, absmax, [0,0,0], m_iMaterial); FX_Explosion(rp); - Damage_Radius(rp, this, m_flExplodeMag, m_flExplodeMag * 2.5f, TRUE, 0); + Damage_Radius(rp, this, m_flExplodeMag, m_flExplodeRad, TRUE, 0); UseTargets(this, TRIG_TOGGLE, 0.0f); /* delay... ignored. */ Hide(); } @@ -299,6 +300,12 @@ func_breakable::Respawn(void) if (!health) { health = 15; } + + if (HasPropData()) { + health = GetPropData(PROPINFO_HEALTH); + m_flExplodeMag = GetPropData(PROPINFO_EXPLOSIVE_DMG); + m_flExplodeRad = GetPropData(PROPINFO_EXPLOSIVE_RADIUS); + } } void @@ -310,6 +317,7 @@ func_breakable::SpawnKey(string strKey, string strValue) break; case "explodemagnitude": m_flExplodeMag = stof(strValue); + m_flExplodeRad = m_flExplodeMag * 2.5f; break; case "spawnobject": int oid = stoi(strValue); diff --git a/src/gs-entbase/shared/baseentity.h b/src/gs-entbase/shared/baseentity.h index 0c1732e2..d27d88ab 100644 --- a/src/gs-entbase/shared/baseentity.h +++ b/src/gs-entbase/shared/baseentity.h @@ -18,6 +18,9 @@ class CBaseEntity { int m_iBody; + /* prop data */ + int m_iPropData; + #ifdef CLIENT float m_flSentenceTime; sound_t *m_pSentenceQue; @@ -62,6 +65,8 @@ class CBaseEntity virtual vector(void) GetSpawnAngles; virtual string(void) GetSpawnModel; virtual float(void) GetSpawnHealth; + virtual int(void) HasPropData; + virtual __variant(int) GetPropData; /* Input/Output System */ string m_strOnTrigger; diff --git a/src/gs-entbase/shared/baseentity.qc b/src/gs-entbase/shared/baseentity.qc index 6e322713..f1d6a43c 100644 --- a/src/gs-entbase/shared/baseentity.qc +++ b/src/gs-entbase/shared/baseentity.qc @@ -816,6 +816,9 @@ CBaseEntity::SpawnInit(void) SpawnKey(argv(i), argv(i+1)); } + /* tokenization complete, now we can load propdata */ + PropData_Finish(); + /* some entity might involuntarily call SpawnInit as part of being a member of CBaseEntity. So we need to make sure that it doesn't inherit stuff from the last previously loaded entity */ @@ -848,6 +851,18 @@ CBaseEntity::Hide(void) SetMovetype(MOVETYPE_NONE); takedamage = DAMAGE_NO; } + +int +CBaseEntity::HasPropData(void) +{ + return (m_iPropData != -1) ? TRUE : FALSE; +} + +__variant +CBaseEntity::GetPropData(int type) +{ + return Prop_GetInfo(m_iPropData, type); +} #endif /* @@ -860,7 +875,14 @@ client doesn't have to do a whole lot here void CBaseEntity::CBaseEntity(void) { + #ifdef SERVER + /* don't call this function more than once per entity */ + if (identity == 1) + return; + + m_iPropData = -1; + /* Not in Deathmatch */ if (spawnflags & 2048) { if (cvar("sv_playerslots") > 1) { @@ -943,6 +965,10 @@ CBaseEntity::SetModel(string newModel) model = newModel; setmodel(this, newModel); SetSendFlags(BASEFL_CHANGED_MODELINDEX); + + if (model && m_iPropData == -1) { + PropData_ForModel(model); + } } void CBaseEntity::SetModelindex(float newModelIndex) @@ -1103,6 +1129,9 @@ CBaseEntity::SpawnKey(string strKey, string strValue) /* we do re-read a lot of the builtin fields in case we want to set defaults. just in case anybody is wondering. */ switch (strKey) { + case "propdata": + PropData_SetStage(strValue); + break; case "scale": scale = stof(strValue); break; diff --git a/src/server/entry.qc b/src/server/entry.qc index 802ff635..eeb7fd6b 100644 --- a/src/server/entry.qc +++ b/src/server/entry.qc @@ -427,6 +427,7 @@ initents(void) /* sound shader init */ Sound_Init(); + PropData_Init(); /* only bother doing so on Half-Life BSP */ if (serverkeyfloat("*bspversion") == BSPVER_HL) { diff --git a/src/server/modelevent.qc b/src/server/modelevent.qc index 016eb25f..aebf96d3 100644 --- a/src/server/modelevent.qc +++ b/src/server/modelevent.qc @@ -39,6 +39,8 @@ Event_ServerModelEvent(float flTimeStamp, int iCode, string strData) } } break; + case 1004: + break; default: dprint(sprintf("^3[SERVER]^7 Unknown model-event code " \ "%i with data %s\n", iCode, strData)); diff --git a/src/shared/defs.h b/src/shared/defs.h index 503013dc..4f5b62c1 100644 --- a/src/shared/defs.h +++ b/src/shared/defs.h @@ -28,6 +28,7 @@ #include "memory.h" #include "spectator.h" #include "platform.h" +#include "propdata.h" #include "vehicles.h" #define BSPVER_PREREL 28 diff --git a/src/shared/include.src b/src/shared/include.src index 0a74c4f0..760f8391 100644 --- a/src/shared/include.src +++ b/src/shared/include.src @@ -6,5 +6,6 @@ sound.qc math.qc player.qc player_pmove.qc +propdata.qc vehicles.qc #endlist diff --git a/src/shared/propdata.h b/src/shared/propdata.h new file mode 100644 index 00000000..126dca8b --- /dev/null +++ b/src/shared/propdata.h @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2016-2021 Marco Hladik + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + Prop-Data specs + + According to Source SDK's propdata.txt we've got + a kinda sorta hacky definition table for this stuff: + + "PropData.txt" + { + + "sometype" + { + // .. key/field attributes + // e.g. + "health" "10" + "breakable_model" "somematerial" + } + + "BreakableModels" + { + // completely unrelated to types defined in propdata.txt + // but somehow still part of it? + + "somematerial" + { + // model path / fadeout time pair + // e.g. + "foo.vvm" "2.5" + } + } + } + + The idea is that props specify the type of prop they are ("sometype") and it defines + a set of sensible defaults. + + However, props can override any parts of this inside the model data itself. + Currently no model format FTEQW supports allows for reading of said propdata. + However we'll be loading "foobar.vvm.propdata" to remedy this for those. +*/ + +var string g_curPropData; + +typedef enumflags +{ + PDFL_BLOCKLOS, /* Does this block an AIs light of sight? */ + PDFL_AIWALKABLE, /* can AI walk on this? */ + PDFL_ALLOWSTATIC /* static simulation possible? */ +} propdataFlag_t; + +typedef struct +{ + string name; + string base; + float health; /* health until break */ + propdataFlag_t flags; + float damage_bullets; /* dmg multipliers */ + float damage_melee; + float damage_explosive; + float explosive_damage; /* once the damage/radius keys are set, make explosion upon break */ + float explosive_radius; + string breakable_model; /* name of BreakableModels entry in PropData.txt */ + int breakable_count; +} propdata_t; + +/* entity will have to have a .propdata field pointing to a propdata id */ +propdata_t *g_propdata; +int g_propdata_count; +var hashtable g_hashpropdata; + +/* necessary API functions */ +void PropData_Init(void); +void PropData_Shutdown(void); + +int PropData_Load(string); /* called when we read entity data, returns -1 when not found */ +int PropData_ForModel(string); /* called when we set a model, returns -1 when not found */ +//int PropData_Read(string); /* this just handles the contents of a prop_data model string */ + +void PropData_SetStage(string); +void PropData_Finish(void); + +/* querying API */ +typedef enum +{ + PROPINFO_HEALTH, + PROPINFO_FLAGS, + PROPINFO_DMG_BULLET, + PROPINFO_DMG_MELEE, + PROPINFO_DMG_EXPLOSIVE, + PROPINFO_EXPLOSIVE_DMG, + PROPINFO_EXPLOSIVE_RADIUS, + PROPINFO_BREAKMODEL, + PROPINFO_BREAKCOUNT +} propinfo_t; +__variant Prop_GetInfo(int, int); + +typedef struct +{ + string model; + float fadetime; +} breakmodel_t; + +/* entity will have a .breakmodel field pointing to a breakmodel id */ +propdata_t *g_breakmodel; +int g_breakmodel_count; + +/* necessary API functions */ +//void BreakModel_Init(void); +//void BreakModel_Shutdown(void); + +//int BreakModel_Load(string); /* called when we precache a model, returns -1 when not found */ +//int BreakModel_Read(string); /* this just handles the contents of a prop_data model string */ diff --git a/src/shared/propdata.qc b/src/shared/propdata.qc new file mode 100644 index 00000000..9a287c67 --- /dev/null +++ b/src/shared/propdata.qc @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2016-2021 Marco Hladik + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +void +PropData_Shutdown(void) +{ + if (g_propdata) { + memfree(g_propdata); + } + + g_propdata_count = 0; + g_hashpropdata = 0; +} + +void +PropData_Init(void) +{ + PropData_Shutdown(); +} + +__variant +Prop_GetInfo(int i, int type) +{ + switch (type) + { + case PROPINFO_HEALTH: + return (__variant)g_propdata[i].health; + case PROPINFO_FLAGS: + return (__variant)g_propdata[i].flags; + case PROPINFO_DMG_BULLET: + return (__variant)g_propdata[i].damage_bullets; + case PROPINFO_DMG_MELEE: + return (__variant)g_propdata[i].damage_melee; + case PROPINFO_DMG_EXPLOSIVE: + return (__variant)g_propdata[i].damage_explosive; + case PROPINFO_EXPLOSIVE_DMG: + return (__variant)g_propdata[i].explosive_damage; + case PROPINFO_EXPLOSIVE_RADIUS: + return (__variant)g_propdata[i].explosive_radius; + case PROPINFO_BREAKMODEL: + return (__variant)g_propdata[i].breakable_model; + case PROPINFO_BREAKCOUNT: + return (__variant)g_propdata[i].breakable_count; + default: + return __NULL__; + } +} + +void +PropData_ParseField(int i, int a) +{ + switch (argv(0)) { + case "base": + g_propdata[i].base = argv(1); + break; + case "blockLOS": + g_propdata[i].flags |= PDFL_BLOCKLOS; + break; + case "AIWalkable": + g_propdata[i].flags |= PDFL_AIWALKABLE; + break; + case "allow_static": + g_propdata[i].flags |= PDFL_ALLOWSTATIC; + break; + case "dmg.bullets": + g_propdata[i].damage_bullets = stof(argv(1)); + break; + case "dmg.club": + g_propdata[i].damage_melee = stof(argv(1)); + break; + case "dmg.explosive": + g_propdata[i].damage_explosive = stof(argv(1)); + break; + case "health": + g_propdata[i].health = stof(argv(1)); + break; + case "explosive_damage": + g_propdata[i].explosive_damage = stof(argv(1)); + break; + case "explosive_radius": + g_propdata[i].explosive_radius = stof(argv(1)); + break; + case "breakable_model": + g_propdata[i].breakable_model = argv(1); + break; + case "breakable_count": + g_propdata[i].breakable_count = stof(argv(1)); + break; + } +} + +/* concerned with dealing with keeping track of braces and parsing lines */ +int +PropData_Parse(int i, string line, string type) +{ + int c; + string key; + static string t_name; + static int braced = 0; + + c = tokenize_console(line); + key = argv(0); + + switch(key) { + case "{": + braced++; + break; + case "}": + braced--; + t_name = ""; + + /* done */ + if (braced == 0) + return (1); + break; + default: + if (braced == 2 && t_name != "") { + PropData_ParseField(i, c); + } else if (braced == 1) { + /* name/identifer of our message */ + t_name = strtolower(key); + + if (t_name == type) { + /* I guess it's what we want */ + g_propdata[i].name = type; + } else { + /* not what we're looking for */ + t_name = ""; + } + } + } + return (0); +} + +/* goes through and looks for a specifically named propdata type inside the scripts dir */ +int +PropData_Load(string type) +{ + searchhandle sh; + filestream fh; + string line; + int index; + + if (!type) + return -1; + + index = g_propdata_count; + type = strtolower(type); + + /* create the hash-table if it doesn't exist */ + if (!g_hashpropdata) { + g_hashpropdata = hash_createtab(2, HASH_ADD); + } + + /* check if it's already cached */ + for (int i = 0; i < g_propdata_count; i++) { + if (type == g_propdata[i].name) { + return i; + } + } + + g_propdata_count++; + g_propdata = (propdata_t *)memrealloc(g_propdata, sizeof(propdata_t), index, g_propdata_count); + + /* Defaults go here */ + + sh = search_begin("scripts/propdata*.txt", TRUE, TRUE); + + for (int i = 0; i < search_getsize(sh); i++) { + fh = fopen(search_getfilename(sh, i), FILE_READ); + if (fh < 0) { + continue; + } + + while ((line = fgets(fh))) { + /* when we found it, quit */ + if (PropData_Parse(index, line, type) == TRUE) { + search_end(sh); + fclose(fh); + hash_add(g_hashpropdata, type, (int)index); + return index; + } + } + fclose(fh); + } + + print("^1[PROPDATA] No type found for "); + print(type); + print("\n"); + + search_end(sh); + return -1; +} + +/* goes through and looks for a specifically named propdata type inside the scripts dir */ +int +PropData_ForModel(string modelname) +{ + filestream fh; + string line; + int index; + + if (!modelname) + return -1; + + if (substring(modelname, 0, 1) == "*") + return -1; + + index = g_propdata_count; + modelname = strtolower(modelname); + + dprint("[PROPDATA] Loading model propdata "); + dprint(modelname); + dprint("\n"); + + /* create the hash-table if it doesn't exist */ + if (!g_hashpropdata) { + g_hashpropdata = hash_createtab(2, HASH_ADD); + } + + /* check if it's already cached */ + for (int i = 0; i < g_propdata_count; i++) { + if (modelname == g_propdata[i].name) { + return i; + } + } + + g_propdata_count++; + g_propdata = (propdata_t *)memrealloc(g_propdata, sizeof(propdata_t), index, g_propdata_count); + + /* Defaults go here */ + + fh = fopen(strcat(modelname, ".propdata"), FILE_READ); + if (fh < 0) { + dprint(sprintf("Can't find propdata for model %s\n", modelname)); + return -1; + } + while ((line = fgets(fh))) { + /* when we found it, quit */ + if (PropData_Parse(index, line, "prop_data") == TRUE) { + fclose(fh); + hash_add(g_hashpropdata, modelname, (int)index); + return index; + } + } + fclose(fh); + + dprint("^1[PROPDATA] No type found for "); + dprint(modelname); + dprint("\n"); + + return -1; +} + +/* we can only tokenize one thing at a time, so we save the type for the current + entity away for later, so we can parse it properly by then when we've exited the + SpawnKey loop. Using a global will save us some memory at least */ +void +PropData_SetStage(string type) +{ + g_curPropData = type; +} + +void +PropData_Finish(void) +{ + PropData_Load(g_curPropData); + g_curPropData = ""; +}