Skip to content

Commit

Permalink
add custom muzzle flashes
Browse files Browse the repository at this point in the history
uses a less configurable temp effect so that the sprite isn't visible for too long
  • Loading branch information
wootguy committed Jan 8, 2025
1 parent 45dbecd commit 7388b45
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 35 deletions.
2 changes: 2 additions & 0 deletions dlls/hooks/hlds_hooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,8 @@ void ServerActivate( edict_t *pEdictList, int edictCount, int clientMax )
}
g_clearInventoriesNextMap = true; // set to false by trigger_changelevel

g_customMuzzleFlashes.clear();

PrintEntindexStats();

g_engfuncs.pfnServerPrint(UTIL_VarArgs("Precache stats: %d models (%d MDL, %d BSP), %d sounds, %d generic, %d events\n",
Expand Down
44 changes: 44 additions & 0 deletions dlls/monster/CBaseMonster.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "lagcomp.h"
#include "CItemInventory.h"
#include "CBasePlayer.h"
#include "CSprite.h"

#define MONSTER_CUT_CORNER_DIST 8 // 8 means the monster's bounding box is contained without the box of the node in WC

Expand Down Expand Up @@ -3024,6 +3025,49 @@ void CBaseMonster::HandleAnimEvent(MonsterEvent_t* pEvent)
break;
}

case SCRIPT_EVENT_MUZZLE_FLASH:
{
custom_muzzle_flash_t flash = loadCustomMuzzleFlash(pEvent->options);

if (flash.sprite) {
Vector origin, angles;

if (flash.attachment != -1) {
GetAttachment(flash.attachment, origin, angles);
}
else if (flash.bone != -1) {
GetBonePosition(flash.bone, origin, angles);
origin = origin + flash.offset;
}
else {
origin = pev->origin;
}

/*
CSprite* spr = CSprite::SpriteCreate(STRING(flash.sprite), origin, TRUE);
spr->pev->rendermode = flash.rendermode;
spr->pev->rendercolor = flash.color.ToVector();
spr->pev->renderamt = flash.color.a;
spr->pev->scale = flash.scale*0.1f;
spr->AnimateUntilDead();
*/

int alphatest = flash.rendermode == 4 ? 1 : 0;

MESSAGE_BEGIN(MSG_PVS, SVC_TEMPENTITY, origin);
WRITE_BYTE(TE_EXPLOSION);
WRITE_COORD(origin.x);
WRITE_COORD(origin.y);
WRITE_COORD(origin.z - 8);
WRITE_SHORT(MODEL_INDEX(STRING(flash.sprite)));
WRITE_BYTE(flash.scale*0.2f);
WRITE_BYTE(50);
WRITE_BYTE(alphatest | 2 | 4 | 8);
MESSAGE_END();
}
break;
}

default:
ALERT(at_aiconsole, "Unhandled animation event %d for %s\n", pEvent->event, STRING(pev->classname));
break;
Expand Down
1 change: 1 addition & 0 deletions dlls/monster/scriptevent.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@
#define SCRIPT_EVENT_SOUND_VOICE 1008 // Play named wave file (on CHAN_VOICE)
#define SCRIPT_EVENT_SENTENCE_RND1 1009 // Play sentence group 25% of the time
#define SCRIPT_EVENT_NOT_DEAD 1010 // Bring back to life (for life/death sequences)
#define SCRIPT_EVENT_MUZZLE_FLASH 5005 // Custom muzzle flash
#endif //SCRIPTEVENT_H
2 changes: 1 addition & 1 deletion dlls/util/animation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ int GetAnimationEvent( void *pmodel, entvars_t *pev, MonsterEvent_t *pMonsterEve
for (; index < pseqdesc->numevents; index++)
{
// Don't send client-side events to the server AI
if ( pevent[index].event >= EVENT_CLIENT )
if ( pevent[index].event >= EVENT_CLIENT && pevent[index].event != 5005)
continue;

if ( (pevent[index].frame >= flStart && pevent[index].frame < flEnd) ||
Expand Down
38 changes: 4 additions & 34 deletions dlls/util/eng_wrappers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -309,40 +309,10 @@ void PRECACHE_MODEL_SEQUENCE(const char* path, studiohdr_t* mdl, int sequence) {
if (evt->event == 5001 || evt->event == 5011 || evt->event == 5021 || evt->event == 5031) { // muzzleflash sprite
PRECACHE_GENERIC(STRING(ALLOC_STRING(normalize_path(opt).c_str())));
}
if (evt->event == 5005) { // custom muzzleflash (sven co-op only, likely requires custom client)
std::string muzzle_txt = normalize_path("events/" + opt);
ALERT(at_console, "unimplemented custom muzzle flash '%s' on model: %s\n",
muzzle_txt.c_str(), path);
/*
PRECACHE_GENERIC(muzzle_txt.c_str());
std::string muzzle_txt_path = getGameFilePath(muzzle_txt.c_str());
if (muzzle_txt_path.empty())
continue;
// parse muzzleflash config for sprite name
std::ifstream file(muzzle_txt_path);
if (file.is_open()) {
int line_num = 0;
std::string line;
while (getline(file, line)) {
line_num++;
line = trimSpaces(line);
if (line.find("//") == 0 || line.length() == 0)
continue;
line = replaceString(line, "\t", " ");
if (line.find("spritename") == 0) {
std::string val = trimSpaces(line.substr(line.find("spritename") + strlen("spritename")));
val.erase(std::remove(val.begin(), val.end(), '\"'), val.end());
PRECACHE_GENERIC(val);
}
}
}
file.close();
*/
if (evt->event == 5005) { // custom muzzleflash
custom_muzzle_flash_t flash = loadCustomMuzzleFlash(opt.c_str());
if (flash.sprite)
PRECACHE_MODEL_ENT(NULL, STRING(flash.sprite));
}
}
}
Expand Down
84 changes: 84 additions & 0 deletions dlls/util/util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ ThreadSafeQueue<AlertMsgCall> g_thread_prints;

std::string g_lastMapName;

std::unordered_map<std::string, custom_muzzle_flash_t> g_customMuzzleFlashes;

TYPEDESCRIPTION gEntvarsDescription[] =
{
DEFINE_ENTITY_FIELD(classname, FIELD_STRING),
Expand Down Expand Up @@ -2536,6 +2538,88 @@ std::unordered_map<std::string, std::string> loadReplacementFile(const char* pat
return replacements;
}

custom_muzzle_flash_t loadCustomMuzzleFlash(const char* path) {
auto cache = g_customMuzzleFlashes.find(path);
if (cache != g_customMuzzleFlashes.end()) {
return cache->second;
}

custom_muzzle_flash_t flash;
memset(&flash, 0, sizeof(custom_muzzle_flash_t));

std::string fpath = getGameFilePath((std::string("events/") + path).c_str());
std::ifstream infile(fpath);

if (fpath.empty() || !infile.is_open()) {
ALERT(at_console, "Failed to load custom muzzle flash file: %s\n", path);
return flash;
}

int lineNum = 0;
std::string line;
while (std::getline(infile, line))
{
lineNum++;
std::string paths[2];

int comments = line.find("//");
if (comments != -1) {
line = line.substr(0, comments);
}

line = trimSpaces(line);
if (line.empty()) {
continue;
}

std::vector<std::string> parts = splitString(line, " \t");

if (parts.empty()) {
continue;
}

std::string name = trimSpaces(toLowerCase(parts[0]));
std::string value = parts.size() > 1 ? trimSpaces(parts[1]) : "";

if (name == "attachment") {
flash.attachment = atoi(value.c_str());
}
else if (name == "bone") {
flash.bone = atoi(value.c_str());
}
else if (name == "spritename") {
flash.sprite = ALLOC_STRING(value.c_str());
}
else if (name == "scale") {
flash.scale = atoi(value.c_str());
}
else if (name == "rendermode") {
flash.rendermode = atoi(value.c_str());
}
else if (name == "colorr") {
flash.color.r = atoi(value.c_str());
}
else if (name == "colorg") {
flash.color.g = atoi(value.c_str());
}
else if (name == "colorb") {
flash.color.b = atoi(value.c_str());
}
else if (name == "transparency") {
flash.color.a = atoi(value.c_str());
}
else if (name == "offset" && parts.size() >= 4) {
flash.offset.x = atof(parts[1].c_str());
flash.offset.y = atof(parts[2].c_str());
flash.offset.z = atof(parts[3].c_str());
}
}

g_customMuzzleFlashes[path] = flash;

return flash;
}

void InitEdictRelocations() {
if (!g_edictsinit) {
// initialize all edict slots so that ents can be relocated anywhere
Expand Down
18 changes: 18 additions & 0 deletions dlls/util/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@ struct RGBA {
RGBA(Vector v) : r(v.x), g(v.y), b(v.z), a(255) {}
RGBA(Vector v, uint8_t a) : r(v.x), g(v.y), b(v.z), a(a) {}
RGBA(RGB rgb) : r(rgb.r), g(rgb.g), b(rgb.b), a(255) {}
RGBA() : r(0), g(0), b(0), a(0) {}

Vector ToVector() { return Vector(r, g, b); }
};

// Use this instead of ALLOC_STRING on constant strings
Expand All @@ -163,6 +166,18 @@ struct RGBA {
(b) = _temp; \
}

struct custom_muzzle_flash_t {
string_t sprite;
uint8_t attachment;
uint8_t bone;
uint8_t scale;
uint8_t rendermode;
RGBA color;
Vector offset;
};

extern std::unordered_map<std::string, custom_muzzle_flash_t> g_customMuzzleFlashes;

// same as the STRING macro but defined as a function for easy calling in the debugger
EXPORT const char* cstr(string_t s);

Expand Down Expand Up @@ -824,6 +839,9 @@ EXPORT std::string getGameFilePath(const char* path);
// format: "file_path" "replacement_file_path"
EXPORT std::unordered_map<std::string, std::string> loadReplacementFile(const char* path);

// loads muzzle flash details from file on the first call, then returns cached results
EXPORT custom_muzzle_flash_t loadCustomMuzzleFlash(const char* path);

EXPORT void te_debug_beam(Vector start, Vector end, uint8_t life, RGBA c, int msgType=MSG_BROADCAST, edict_t* dest=NULL);

//
Expand Down

0 comments on commit 7388b45

Please sign in to comment.