From d9382f2100d001d9e3283f4d798542216144babc Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Wed, 26 Apr 2023 09:47:39 -0400 Subject: [PATCH] ACS: UDMF user properties Lines, sides, sectors, and things now all support the "Custom" tab properly. Label a property as `user_[whatever you want]` in this tab, and it will be added to the structure. ACS will then be able to retrieve it using the `Get[x]UserProperty()` function. --- src/CMakeLists.txt | 1 + src/acs/call-funcs.cpp | 265 ++++++++++++++++++++++++++++++++++++++++ src/acs/call-funcs.hpp | 5 + src/acs/environment.cpp | 8 +- src/doomdata.h | 16 +++ src/k_mapuser.c | 180 +++++++++++++++++++++++++++ src/k_mapuser.h | 94 ++++++++++++++ src/p_setup.c | 242 ++++++++++++++++++++++++++++++++++++ src/r_defs.h | 11 ++ src/typedef.h | 4 + 10 files changed, 822 insertions(+), 4 deletions(-) create mode 100644 src/k_mapuser.c create mode 100644 src/k_mapuser.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 81c871de6..cc9a5acac 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -144,6 +144,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32 k_vote.c k_serverstats.c k_zvote.c + k_mapuser.c ) if(SRB2_CONFIG_ENABLE_WEBM_MOVIES) diff --git a/src/acs/call-funcs.cpp b/src/acs/call-funcs.cpp index 1d24e709f..41b59e56b 100644 --- a/src/acs/call-funcs.cpp +++ b/src/acs/call-funcs.cpp @@ -2226,3 +2226,268 @@ bool CallFunc_SetSectorProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, return false; } + +/*-------------------------------------------------- + bool CallFunc_Get[x]UserProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + User-defined property management. +--------------------------------------------------*/ +bool CallFunc_GetLineUserProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + auto info = &static_cast(thread)->info; + Environment *env = &ACSEnv; + + mtag_t tag = 0; + INT32 lineID = 0; + INT32 activatorID = -1; + line_t *line = NULL; + + const char *key = NULL; + + mapUserProperty_t *prop = NULL; + INT32 ret = 0; + + tag = argV[0]; + key = thread->scopeMap->getString(argV[1])->str; + + if (info != NULL && info->line != NULL) + { + activatorID = info->line - lines; + } + + if ((lineID = NextLine(tag, lineID, activatorID)) != -1) + { + line = &lines[ lineID ]; + } + + if (line != NULL) + { + prop = K_UserPropertyFind(&line->user, key); + } + + if (prop != NULL) + { + switch (prop->type) + { + case USER_PROP_BOOL: + { + ret = static_cast(prop->valueBool); + break; + } + case USER_PROP_INT: + { + ret = prop->valueInt; + break; + } + case USER_PROP_FIXED: + { + ret = static_cast(prop->valueFixed); + break; + } + case USER_PROP_STR: + { + ret = static_cast( ~env->getString( prop->valueStr )->idx ); + break; + } + } + } + + thread->dataStk.push(ret); + return false; +} + +bool CallFunc_GetSideUserProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + auto info = &static_cast(thread)->info; + Environment *env = &ACSEnv; + + mtag_t tag = 0; + INT32 lineID = 0; + INT32 activatorID = -1; + line_t *line = NULL; + + UINT8 sideID = 0; + side_t *side = NULL; + + const char *key = NULL; + + mapUserProperty_t *prop = NULL; + INT32 ret = 0; + + tag = argV[0]; + sideID = argV[1]; + key = thread->scopeMap->getString(argV[2])->str; + + if (info != NULL && info->line != NULL) + { + activatorID = info->line - lines; + } + + if ((lineID = NextLine(tag, lineID, activatorID)) != -1) + { + line = &lines[ lineID ]; + } + + if (sideID < 0 || sideID > 1) + { + sideID = info->side; + } + + if (line != NULL && line->sidenum[sideID] != 0xffff) + { + side = &sides[line->sidenum[sideID]]; + } + + if (side != NULL) + { + prop = K_UserPropertyFind(&side->user, key); + } + + if (prop != NULL) + { + switch (prop->type) + { + case USER_PROP_BOOL: + { + ret = static_cast(prop->valueBool); + break; + } + case USER_PROP_INT: + { + ret = prop->valueInt; + break; + } + case USER_PROP_FIXED: + { + ret = static_cast(prop->valueFixed); + break; + } + case USER_PROP_STR: + { + ret = static_cast( ~env->getString( prop->valueStr )->idx ); + break; + } + } + } + + thread->dataStk.push(ret); + return false; +} + +bool CallFunc_GetSectorUserProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + auto info = &static_cast(thread)->info; + Environment *env = &ACSEnv; + + mtag_t tag = 0; + INT32 sectorID = 0; + INT32 activatorID = -1; + sector_t *sector = NULL; + + const char *key = NULL; + + mapUserProperty_t *prop = NULL; + INT32 ret = 0; + + tag = argV[0]; + key = thread->scopeMap->getString(argV[1])->str; + + if (info != NULL && info->sector != NULL) + { + activatorID = info->sector - sectors; + } + + if ((sectorID = NextSector(tag, sectorID, activatorID)) != -1) + { + sector = §ors[ sectorID ]; + } + + if (sector != NULL) + { + prop = K_UserPropertyFind(§or->user, key); + } + + if (prop != NULL) + { + switch (prop->type) + { + case USER_PROP_BOOL: + { + ret = static_cast(prop->valueBool); + break; + } + case USER_PROP_INT: + { + ret = prop->valueInt; + break; + } + case USER_PROP_FIXED: + { + ret = static_cast(prop->valueFixed); + break; + } + case USER_PROP_STR: + { + ret = static_cast( ~env->getString( prop->valueStr )->idx ); + break; + } + } + } + + thread->dataStk.push(ret); + return false; +} + +bool CallFunc_GetThingUserProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + auto info = &static_cast(thread)->info; + Environment *env = &ACSEnv; + + mtag_t tag = 0; + mobj_t *mobj = NULL; + + const char *key = NULL; + + mapUserProperty_t *prop = NULL; + INT32 ret = 0; + + tag = argV[0]; + key = thread->scopeMap->getString(argV[1])->str; + + mobj = P_FindMobjFromTID(tag, mobj, info->mo); + + if (mobj != NULL && mobj->spawnpoint != NULL) + { + prop = K_UserPropertyFind(&mobj->spawnpoint->user, key); + } + + if (prop != NULL) + { + switch (prop->type) + { + case USER_PROP_BOOL: + { + ret = static_cast(prop->valueBool); + break; + } + case USER_PROP_INT: + { + ret = prop->valueInt; + break; + } + case USER_PROP_FIXED: + { + ret = static_cast(prop->valueFixed); + break; + } + case USER_PROP_STR: + { + ret = static_cast( ~env->getString( prop->valueStr )->idx ); + break; + } + } + } + + thread->dataStk.push(ret); + return false; +} diff --git a/src/acs/call-funcs.hpp b/src/acs/call-funcs.hpp index 154ee1fbd..dd64df5c0 100644 --- a/src/acs/call-funcs.hpp +++ b/src/acs/call-funcs.hpp @@ -90,4 +90,9 @@ bool CallFunc_SetSideProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, AC bool CallFunc_GetSectorProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_SetSectorProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_GetLineUserProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_GetSideUserProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_GetSectorUserProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_GetThingUserProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); + #endif // __SRB2_ACS_CALL_FUNCS_HPP__ diff --git a/src/acs/environment.cpp b/src/acs/environment.cpp index 0840da59a..dbaaa9514 100644 --- a/src/acs/environment.cpp +++ b/src/acs/environment.cpp @@ -130,16 +130,16 @@ Environment::Environment() // ACS implementations. addFuncDataACS0( 1, addCallFunc(CallFunc_GetLineProperty)); addFuncDataACS0( 2, addCallFunc(CallFunc_SetLineProperty)); - //addFuncDataACS0( 3, addCallFunc(CallFunc_GetLineUserProperty)); + addFuncDataACS0( 3, addCallFunc(CallFunc_GetLineUserProperty)); addFuncDataACS0( 4, addCallFunc(CallFunc_GetSectorProperty)); addFuncDataACS0( 5, addCallFunc(CallFunc_SetSectorProperty)); - //addFuncDataACS0( 6, addCallFunc(CallFunc_GetSectorUserProperty)); + addFuncDataACS0( 6, addCallFunc(CallFunc_GetSectorUserProperty)); addFuncDataACS0( 7, addCallFunc(CallFunc_GetSideProperty)); addFuncDataACS0( 8, addCallFunc(CallFunc_SetSideProperty)); - //addFuncDataACS0( 9, addCallFunc(CallFunc_GetSideUserProperty)); + addFuncDataACS0( 9, addCallFunc(CallFunc_GetSideUserProperty)); //addFuncDataACS0( 10, addCallFunc(CallFunc_GetThingProperty)); //addFuncDataACS0( 11, addCallFunc(CallFunc_SetThingProperty)); - //addFuncDataACS0( 12, addCallFunc(CallFunc_GetThingUserProperty)); + addFuncDataACS0( 12, addCallFunc(CallFunc_GetThingUserProperty)); //addFuncDataACS0( 13, addCallFunc(CallFunc_GetPlayerProperty)); //addFuncDataACS0( 14, addCallFunc(CallFunc_SetPlayerProperty)); //addFuncDataACS0( 15, addCallFunc(CallFunc_GetPolyobjProperty)); diff --git a/src/doomdata.h b/src/doomdata.h index c7395727f..f5e617a0f 100644 --- a/src/doomdata.h +++ b/src/doomdata.h @@ -233,6 +233,21 @@ struct mapnode_t #pragma pack() #endif +typedef enum +{ + USER_PROP_BOOL, + USER_PROP_INT, + USER_PROP_FIXED, + USER_PROP_STR +} mapUserPropertyType_e; + +struct mapUserProperties_t +{ + mapUserProperty_t *properties; + size_t length; + size_t capacity; +}; + #define NUMMAPTHINGARGS 10 #define NUMMAPTHINGSTRINGARGS 2 @@ -252,6 +267,7 @@ struct mapthing_t INT32 args[NUMMAPTHINGARGS]; char *stringargs[NUMMAPTHINGSTRINGARGS]; UINT8 layer; // FOF layer to spawn on, see P_GetMobjSpawnHeight + mapUserProperties_t user; // UDMF user-defined custom properties. mobj_t *mobj; }; diff --git a/src/k_mapuser.c b/src/k_mapuser.c new file mode 100644 index 000000000..a19d1c62e --- /dev/null +++ b/src/k_mapuser.c @@ -0,0 +1,180 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) by Sally "TehRealSalt" Cochenour +// Copyright (C) by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file k_mapuser.c +/// \brief UDMF: Custom user properties + +#include "k_mapuser.h" + +#include "doomdef.h" +#include "doomstat.h" +#include "doomdata.h" +#include "doomtype.h" +#include "z_zone.h" +#include "m_misc.h" +#include "m_fixed.h" + +/*-------------------------------------------------- + void K_UserPropertyPush(mapUserProperties_t *user, const char *key, mapUserPropertyType_e type, void *value) + + See header file for description. +--------------------------------------------------*/ +void K_UserPropertyPush(mapUserProperties_t *user, const char *key, mapUserPropertyType_e type, void *value) +{ + const size_t keyLength = strlen(key); + mapUserProperty_t *prop = NULL; + + I_Assert(keyLength > 0); + + if (user->length >= user->capacity) + { + if (user->capacity == 0) + { + user->capacity = 8; + } + else + { + user->capacity *= 2; + } + + user->properties = (mapUserProperty_t *)Z_ReallocAlign( + user->properties, + sizeof(mapUserProperty_t) * user->capacity, + PU_STATIC, // PU_LEVEL + NULL, + sizeof(mapUserProperty_t) * 8 + ); + } + + prop = &user->properties[ user->length ]; + + prop->key = Z_Malloc(keyLength + 1, PU_STATIC, NULL); + M_Memcpy(prop->key, key, keyLength + 1); + prop->key[keyLength] = '\0'; + + prop->hash = quickncasehash(prop->key, keyLength); + + prop->type = type; + switch (type) + { + case USER_PROP_BOOL: + { + prop->valueBool = *(boolean *)value; + break; + } + case USER_PROP_INT: + { + prop->valueInt = *(INT32 *)value; + break; + } + case USER_PROP_FIXED: + { + prop->valueFixed = *(fixed_t *)value; + break; + } + case USER_PROP_STR: + { + const char *string = *(const char **)value; + const size_t stringLength = strlen(string); + + prop->valueStr = Z_Malloc(stringLength + 1, PU_STATIC, NULL); + M_Memcpy(prop->valueStr, string, stringLength + 1); + prop->valueStr[stringLength] = '\0'; + break; + } + } + + user->length++; +} + +/*-------------------------------------------------- + mapUserProperty_t *K_UserPropertyFind(mapUserProperties_t *user, const char *key) + + See header file for description. +--------------------------------------------------*/ +mapUserProperty_t *K_UserPropertyFind(mapUserProperties_t *user, const char *key) +{ + const size_t keyLength = strlen(key); + const UINT32 hash = quickncasehash(key, keyLength); + size_t i; + + if (user->length == 0) + { + return NULL; + } + + for (i = 0; i < user->length; i++) + { + mapUserProperty_t *const prop = &user->properties[ i ]; + + if (hash != prop->hash) + { + continue; + } + + if (strcasecmp(key, prop->key)) + { + continue; + } + + return prop; + } + + return NULL; +} + +/*-------------------------------------------------- + static void K_UserPropertyFree(mapUserProperty_t *prop) + + Frees the memory of a single user property. + + Input Arguments:- + prop - User property memory structure to free. + + Return:- + N/A +--------------------------------------------------*/ +static void K_UserPropertyFree(mapUserProperty_t *prop) +{ + if (prop->key != NULL) + { + Z_Free(prop->key); + prop->key = NULL; + } + + if (prop->valueStr != NULL) + { + Z_Free(prop->valueStr); + prop->valueStr = NULL; + } +} + +/*-------------------------------------------------- + void K_UserPropertiesClear(mapUserProperties_t *user) + + See header file for description. +--------------------------------------------------*/ +void K_UserPropertiesClear(mapUserProperties_t *user) +{ + size_t i; + + if (user->properties == NULL) + { + return; + } + + for (i = 0; i < user->length; i++) + { + K_UserPropertyFree(&user->properties[i]); + } + + Z_Free(user->properties); + user->properties = NULL; + user->length = user->capacity = 0; +} diff --git a/src/k_mapuser.h b/src/k_mapuser.h new file mode 100644 index 000000000..6bbb6e516 --- /dev/null +++ b/src/k_mapuser.h @@ -0,0 +1,94 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) by Sally "TehRealSalt" Cochenour +// Copyright (C) by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file k_mapuser.h +/// \brief UDMF: Custom user properties + +#ifndef __K_MAPUSER__ +#define __K_MAPUSER__ + +#include "doomdef.h" +#include "doomstat.h" +#include "doomdata.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct mapUserProperty_t +{ + UINT32 hash; + char *key; + + mapUserPropertyType_e type; + + boolean valueBool; + INT32 valueInt; + fixed_t valueFixed; + char *valueStr; +}; + +// mapUserProperties_t has to be defined in doomdata.h instead +// because of circular dependency issues + +/*-------------------------------------------------- + void K_UserPropertyPush(mapUserProperties_t *user, const char *key, mapUserPropertyType_e type, void *value); + + Adds a new key value to the user properties struct. + + Input Arguments:- + user - User properties memory structure. + key - The key to add. + type - Enum representing the type of the value to add. + value - The value to add, as a void pointer. + + Return:- + N/A +--------------------------------------------------*/ + +void K_UserPropertyPush(mapUserProperties_t *user, const char *key, mapUserPropertyType_e type, void *value); + + +/*-------------------------------------------------- + mapUserProperty_t *K_UserPropertyFind(mapUserProperties_t *user, const char *key); + + Tries to find if the key value already exists in this + user properties struct. + + Input Arguments:- + user - User properties memory structure. + key - The key to find. + + Return:- + The struct of the property we just got, or NULL if the key didn't exist already. +--------------------------------------------------*/ + +mapUserProperty_t *K_UserPropertyFind(mapUserProperties_t *user, const char *key); + + +/*-------------------------------------------------- + void K_UserPropertiesClear(mapUserProperties_t *user); + + Frees an entire user properties struct. + + Input Arguments:- + user - User properties memory structure to free. + + Return:- + N/A +--------------------------------------------------*/ + +void K_UserPropertiesClear(mapUserProperties_t *user); + + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // __K_MAPUSER__ diff --git a/src/p_setup.c b/src/p_setup.c index 27f523e25..7d07e7c18 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -103,6 +103,7 @@ #include "doomstat.h" // MAXMUSNAMES #include "k_podium.h" #include "k_rank.h" +#include "k_mapuser.h" // Replay names have time #if !defined (UNDER_CE) @@ -1388,6 +1389,80 @@ static boolean TextmapCount(size_t size) return true; } +enum +{ + PROP_NUM_TYPE_NA, + PROP_NUM_TYPE_INT, + PROP_NUM_TYPE_FLOAT +}; + +static void ParseUserProperty(mapUserProperties_t *user, const char *param, const char *val) +{ + if (fastncmp(param, "user_", 5) && strlen(param) > 5) + { + const char *key = param + 5; + const size_t valLen = strlen(val); + UINT8 numberType = PROP_NUM_TYPE_INT; + size_t i = 0; + + for (i = 0; i < valLen; i++) + { + if (val[i] == '.') + { + numberType = PROP_NUM_TYPE_FLOAT; + } + else if (val[i] < '0' || val[i] > '9') + { + numberType = PROP_NUM_TYPE_NA; + break; + } + } + + switch (numberType) + { + case PROP_NUM_TYPE_NA: + default: + { + // Value is a boolean or a string. + + // Unfortunately, our UDMF parser discards the quotation marks, + // so we can't differentiate strings from booleans properly. + // Don't feel like tearing it apart to fix that, so we just + // turn true & false into booleans, even if they were actually + // supposed to be text. + + boolean vBool = fastcmp("true", val); + if (vBool == true || fastcmp("false", val)) + { + // Value is *probably* a boolean. + K_UserPropertyPush(user, key, USER_PROP_BOOL, &vBool); + } + else + { + // Value is a string. + K_UserPropertyPush(user, key, USER_PROP_STR, &val); + } + break; + } + case PROP_NUM_TYPE_INT: + { + // Value is an integer. + INT32 vInt = atol(val); + K_UserPropertyPush(user, key, USER_PROP_INT, &vInt); + break; + } + case PROP_NUM_TYPE_FLOAT: + { + // Value is a float. Convert to fixed. + fixed_t vFixed = FLOAT_TO_FIXED(atof(val)); + K_UserPropertyPush(user, key, USER_PROP_FIXED, &vFixed); + break; + } + + } + } +} + static void ParseTextmapVertexParameter(UINT32 i, const char *param, const char *val) { if (fastcmp(param, "x")) @@ -1657,6 +1732,8 @@ static void ParseTextmapSectorParameter(UINT32 i, const char *param, const char sectors[i].activation |= SECSPAC_FLOORMISSILE; else if (fastcmp(param, "missileceiling") && fastcmp("true", val)) sectors[i].activation |= SECSPAC_CEILINGMISSILE; + else + ParseUserProperty(§ors[i].user, param, val); } static void ParseTextmapSidedefParameter(UINT32 i, const char *param, const char *val) @@ -1675,6 +1752,8 @@ static void ParseTextmapSidedefParameter(UINT32 i, const char *param, const char P_SetSidedefSector(i, atol(val)); else if (fastcmp(param, "repeatcnt")) sides[i].repeatcnt = atol(val); + else + ParseUserProperty(&sides[i].user, param, val); } static void ParseTextmapLinedefParameter(UINT32 i, const char *param, const char *val) @@ -1782,6 +1861,8 @@ static void ParseTextmapLinedefParameter(UINT32 i, const char *param, const char lines[i].activation |= SPAC_PUSHMONSTER; else if (fastcmp(param, "impact") && fastcmp("true", val)) lines[i].activation |= SPAC_IMPACT; + else + ParseUserProperty(&lines[i].user, param, val); } static void ParseTextmapThingParameter(UINT32 i, const char *param, const char *val) @@ -1839,6 +1920,8 @@ static void ParseTextmapThingParameter(UINT32 i, const char *param, const char * return; mapthings[i].args[argnum] = atol(val); } + else + ParseUserProperty(&mapthings[i].user, param, val); } /** From a given position table, run a specified parser function through a {}-encapsuled text. @@ -1977,13 +2060,35 @@ static void P_WriteTextmap(void) memcpy(wsides, sides, numsides * sizeof(*sides)); for (i = 0; i < nummapthings; i++) + { if (mapthings[i].tags.count) wmapthings[i].tags.tags = memcpy(Z_Malloc(mapthings[i].tags.count * sizeof(mtag_t), PU_LEVEL, NULL), mapthings[i].tags.tags, mapthings[i].tags.count * sizeof(mtag_t)); + if (mapthings[i].user.length) + { + wmapthings[i].user.properties = memcpy( + Z_Malloc(mapthings[i].user.length * sizeof(mapUserProperty_t), PU_LEVEL, NULL), + mapthings[i].user.properties, + mapthings[i].user.length * sizeof(mapUserProperty_t) + ); + } + } + for (i = 0; i < numsectors; i++) + { if (sectors[i].tags.count) wsectors[i].tags.tags = memcpy(Z_Malloc(sectors[i].tags.count*sizeof(mtag_t), PU_LEVEL, NULL), sectors[i].tags.tags, sectors[i].tags.count*sizeof(mtag_t)); + if (sectors[i].user.length) + { + wsectors[i].user.properties = memcpy( + Z_Malloc(sectors[i].user.length * sizeof(mapUserProperty_t), PU_LEVEL, NULL), + sectors[i].user.properties, + sectors[i].user.length * sizeof(mapUserProperty_t) + ); + } + } + for (i = 0; i < numlines; i++) { size_t v; @@ -1991,6 +2096,15 @@ static void P_WriteTextmap(void) if (lines[i].tags.count) wlines[i].tags.tags = memcpy(Z_Malloc(lines[i].tags.count * sizeof(mtag_t), PU_LEVEL, NULL), lines[i].tags.tags, lines[i].tags.count * sizeof(mtag_t)); + if (lines[i].user.length) + { + wlines[i].user.properties = memcpy( + Z_Malloc(lines[i].user.length * sizeof(mapUserProperty_t), PU_LEVEL, NULL), + lines[i].user.properties, + lines[i].user.length * sizeof(mapUserProperty_t) + ); + } + v = lines[i].v1 - vertexes; wusedvertexes[v] = true; @@ -1998,6 +2112,18 @@ static void P_WriteTextmap(void) wusedvertexes[v] = true; } + for (i = 0; i < numsides; i++) + { + if (sides[i].user.length) + { + wsides[i].user.properties = memcpy( + Z_Malloc(sides[i].user.length * sizeof(mapUserProperty_t), PU_LEVEL, NULL), + sides[i].user.properties, + sides[i].user.length * sizeof(mapUserProperty_t) + ); + } + } + freetag = Tag_NextUnused(0); for (i = 0; i < nummapthings; i++) @@ -2279,6 +2405,28 @@ static void P_WriteTextmap(void) for (j = 0; j < NUMMAPTHINGSTRINGARGS; j++) if (mapthings[i].stringargs[j]) fprintf(f, "stringarg%s = \"%s\";\n", sizeu1(j), mapthings[i].stringargs[j]); + if (wmapthings[i].user.length > 0) + { + for (j = 0; j < wmapthings[i].user.length; j++) + { + mapUserProperty_t *const prop = &wmapthings[i].user.properties[j]; + switch (prop->type) + { + case USER_PROP_BOOL: + fprintf(f, "user_%s = %s;\n", prop->key, (prop->valueBool == true) ? "true" : "false"); + break; + case USER_PROP_INT: + fprintf(f, "user_%s = %d;\n", prop->key, prop->valueInt); + break; + case USER_PROP_FIXED: + fprintf(f, "user_%s = %f;\n", prop->key, FIXED_TO_FLOAT(prop->valueFixed)); + break; + case USER_PROP_STR: + fprintf(f, "user_%s = \"%s\";\n", prop->key, prop->valueStr); + break; + } + } + } fprintf(f, "}\n"); fprintf(f, "\n"); } @@ -2405,6 +2553,28 @@ static void P_WriteTextmap(void) fprintf(f, "monsterpush = true;\n"); if (wlines[i].activation & SPAC_IMPACT) fprintf(f, "impact = true;\n"); + if (wlines[i].user.length > 0) + { + for (j = 0; j < wlines[i].user.length; j++) + { + mapUserProperty_t *const prop = &wlines[i].user.properties[j]; + switch (prop->type) + { + case USER_PROP_BOOL: + fprintf(f, "user_%s = %s;\n", prop->key, (prop->valueBool == true) ? "true" : "false"); + break; + case USER_PROP_INT: + fprintf(f, "user_%s = %d;\n", prop->key, prop->valueInt); + break; + case USER_PROP_FIXED: + fprintf(f, "user_%s = %f;\n", prop->key, FIXED_TO_FLOAT(prop->valueFixed)); + break; + case USER_PROP_STR: + fprintf(f, "user_%s = \"%s\";\n", prop->key, prop->valueStr); + break; + } + } + } fprintf(f, "}\n"); fprintf(f, "\n"); } @@ -2426,6 +2596,28 @@ static void P_WriteTextmap(void) fprintf(f, "texturemiddle = \"%.*s\";\n", 8, textures[wsides[i].midtexture]->name); if (wsides[i].repeatcnt != 0) fprintf(f, "repeatcnt = %d;\n", wsides[i].repeatcnt); + if (wsides[i].user.length > 0) + { + for (j = 0; j < wsides[i].user.length; j++) + { + mapUserProperty_t *const prop = &wsides[i].user.properties[j]; + switch (prop->type) + { + case USER_PROP_BOOL: + fprintf(f, "user_%s = %s;\n", prop->key, (prop->valueBool == true) ? "true" : "false"); + break; + case USER_PROP_INT: + fprintf(f, "user_%s = %d;\n", prop->key, prop->valueInt); + break; + case USER_PROP_FIXED: + fprintf(f, "user_%s = %f;\n", prop->key, FIXED_TO_FLOAT(prop->valueFixed)); + break; + case USER_PROP_STR: + fprintf(f, "user_%s = \"%s\";\n", prop->key, prop->valueStr); + break; + } + } + } fprintf(f, "}\n"); fprintf(f, "\n"); } @@ -2609,6 +2801,28 @@ static void P_WriteTextmap(void) fprintf(f, "missilefloor = true;\n"); if (wsectors[i].activation & SECSPAC_CEILINGMISSILE) fprintf(f, "missileceiling = true;\n"); + if (wsectors[i].user.length > 0) + { + for (j = 0; j < wsectors[i].user.length; j++) + { + mapUserProperty_t *const prop = &wsectors[i].user.properties[j]; + switch (prop->type) + { + case USER_PROP_BOOL: + fprintf(f, "user_%s = %s;\n", prop->key, (prop->valueBool == true) ? "true" : "false"); + break; + case USER_PROP_INT: + fprintf(f, "user_%s = %d;\n", prop->key, prop->valueInt); + break; + case USER_PROP_FIXED: + fprintf(f, "user_%s = %f;\n", prop->key, FIXED_TO_FLOAT(prop->valueFixed)); + break; + case USER_PROP_STR: + fprintf(f, "user_%s = \"%s\";\n", prop->key, prop->valueStr); + break; + } + } + } fprintf(f, "}\n"); fprintf(f, "\n"); } @@ -2616,17 +2830,38 @@ static void P_WriteTextmap(void) fclose(f); for (i = 0; i < nummapthings; i++) + { if (wmapthings[i].tags.count) Z_Free(wmapthings[i].tags.tags); + if (wmapthings[i].user.length) + Z_Free(wmapthings[i].user.properties); + } + for (i = 0; i < numsectors; i++) + { if (wsectors[i].tags.count) Z_Free(wsectors[i].tags.tags); + if (wsectors[i].user.length) + Z_Free(wsectors[i].user.properties); + } + for (i = 0; i < numlines; i++) + { if (wlines[i].tags.count) Z_Free(wlines[i].tags.tags); + if (wlines[i].user.length) + Z_Free(wlines[i].user.properties); + } + + for (i = 0; i < numsides; i++) + { + if (wsides[i].user.length) + Z_Free(wsides[i].user.properties); + } + Z_Free(wmapthings); Z_Free(wvertexes); Z_Free(wsectors); @@ -2709,6 +2944,8 @@ static void P_LoadTextmap(void) memset(sc->stringargs, 0x00, NUMSECTORSTRINGARGS*sizeof(*sc->stringargs)); sc->activation = 0; + K_UserPropertiesClear(&sc->user); + textmap_colormap.used = false; textmap_colormap.lightcolor = 0; textmap_colormap.lightalpha = 25; @@ -2762,6 +2999,7 @@ static void P_LoadTextmap(void) ld->sidenum[1] = 0xffff; ld->activation = 0; + K_UserPropertiesClear(&ld->user); TextmapParse(linesPos[i], i, ParseTextmapLinedefParameter); @@ -2786,6 +3024,8 @@ static void P_LoadTextmap(void) sd->sector = NULL; sd->repeatcnt = 0; + K_UserPropertiesClear(&sd->user); + TextmapParse(sidesPos[i], i, ParseTextmapSidedefParameter); if (!sd->sector) @@ -2811,6 +3051,8 @@ static void P_LoadTextmap(void) mt->layer = 0; mt->mobj = NULL; + K_UserPropertiesClear(&mt->user); + TextmapParse(mapthingsPos[i], i, ParseTextmapThingParameter); } } diff --git a/src/r_defs.h b/src/r_defs.h index 72892fc9e..ec4371e75 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -30,6 +30,8 @@ #include "taglist.h" +#include "k_mapuser.h" + #ifdef __cplusplus extern "C" { #endif @@ -551,6 +553,9 @@ struct sector_t INT32 args[NUMSECTORARGS]; char *stringargs[NUMSECTORSTRINGARGS]; sectoractionflags_t activation; + + // UDMF user-defined custom properties. + mapUserProperties_t user; }; // @@ -609,6 +614,9 @@ struct line_t char *text; // a concatenation of all front and back texture names, for linedef specials that require a string. INT16 callcount; // no. of calls left before triggering, for the "X calls" linedef specials, defaults to 0 + + // UDMF user-defined custom properties. + mapUserProperties_t user; }; struct side_t @@ -635,6 +643,9 @@ struct side_t char *text; // a concatenation of all top, bottom, and mid texture names, for linedef specials that require a string. extracolormap_t *colormap_data; // storage for colormaps; not applied to sectors. + + // UDMF user-defined custom properties. + mapUserProperties_t user; }; // diff --git a/src/typedef.h b/src/typedef.h index 25e2910e1..ebe816913 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -217,6 +217,10 @@ TYPEDEF (gpRank_t); TYPEDEF (midVote_t); TYPEDEF (midVoteGUI_t); +// k_mapuser.h +TYPEDEF (mapUserProperty_t); +TYPEDEF (mapUserProperties_t); + // lua_hudlib_drawlist.h typedef struct huddrawlist_s *huddrawlist_h;