Implement and parse the propdata system from Source 2004 and make our

func_breakable entity aware of it.
This commit is contained in:
Marco Cawthorne 2021-09-17 19:31:22 +02:00
parent 3cc01ca512
commit 3bceff6a2c
Signed by: eukara
GPG Key ID: C196CD8BA993248A
11 changed files with 464 additions and 2 deletions

View File

@ -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();
}

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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) {

View File

@ -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));

View File

@ -28,6 +28,7 @@
#include "memory.h"
#include "spectator.h"
#include "platform.h"
#include "propdata.h"
#include "vehicles.h"
#define BSPVER_PREREL 28

View File

@ -6,5 +6,6 @@ sound.qc
math.qc
player.qc
player_pmove.qc
propdata.qc
vehicles.qc
#endlist

126
src/shared/propdata.h Normal file
View File

@ -0,0 +1,126 @@
/*
* Copyright (c) 2016-2021 Marco Hladik <marco@icculus.org>
*
* 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 */

282
src/shared/propdata.qc Normal file
View File

@ -0,0 +1,282 @@
/*
* Copyright (c) 2016-2021 Marco Hladik <marco@icculus.org>
*
* 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 = "";
}