diff --git a/src/dlls/Makefile b/src/dlls/Makefile
index 2c5b3ed..1a1e854 100644
--- a/src/dlls/Makefile
+++ b/src/dlls/Makefile
@@ -60,6 +60,7 @@ OBJ = \
util.o \
voltigore.o \
weapons.o \
+ xenmaker.o \
zombie.o
monster_mm_i386.so: ${OBJ}
diff --git a/src/dlls/cmbaseextra.h b/src/dlls/cmbaseextra.h
index cabde6f..5712e82 100644
--- a/src/dlls/cmbaseextra.h
+++ b/src/dlls/cmbaseextra.h
@@ -48,4 +48,46 @@ public:
BOOL m_fPlaying; // music is active
};
+//=========================================================
+// XenMaker - spawns a monster with a teleportation effect.
+//=========================================================
+class CMXenMaker : public CMBaseMonster
+{
+public:
+ void Spawn(void);
+ void Precache(void);
+ void KeyValue(KeyValueData* pkvd);
+ void EXPORT CyclicUse(edict_t *pActivator, edict_t *pCaller, USE_TYPE useType, float value);
+ void EXPORT RetryThink(void);
+ void StartEffect(void);
+ void EXPORT MiddleEffect(void);
+ void EXPORT EndEffect(void);
+
+ int m_iMonsterIndex;// index of the monster that will be created.
+
+ float m_flGround; // z coord of the ground under me, used to make sure no monsters are under the spawner
+
+ float m_flBeamRadius; // Maximum beam strike radius.
+ int m_iBeamAlpha;
+ int m_iBeamCount; // Number of single beam instances.
+ Vector m_vBeamColor;
+
+ float m_flLightRadius;
+ Vector m_vLightColor;
+
+ float m_flStartSpriteFramerate;
+ float m_flStartSpriteScale;
+ int m_iStartSpriteAlpha;
+ Vector m_vStartSpriteColor;
+
+ float m_flEndSpriteFramerate;
+ float m_flEndSpriteScale;
+ int m_iEndSpriteAlpha;
+ Vector m_vEndSpriteColor;
+
+private:
+ void SpawnBeam(void);
+ int m_iBeamIndex;
+};
+
#endif // BASEEXTRA_H
diff --git a/src/dlls/dllapi.cpp b/src/dlls/dllapi.cpp
index edbfa93..88f3ce5 100644
--- a/src/dlls/dllapi.cpp
+++ b/src/dlls/dllapi.cpp
@@ -166,6 +166,7 @@ monster_type_t monster_types[]=
"info_node_air", FALSE,
"monstermaker", FALSE, // Extra entities
"ambient_music", FALSE,
+ "env_xenmaker", FALSE,
"squadmaker", FALSE, // Aliases
"", FALSE
};
@@ -711,6 +712,7 @@ edict_t* spawn_monster(int monster_type, Vector origin, Vector angles, int spawn
// Extra entities
case 32: monsters[monster_index].pMonster = CreateClassPtr((CMMonsterMaker *)NULL); break;
case 33: monsters[monster_index].pMonster = CreateClassPtr((CMAmbientMusic *)NULL); break;
+ case 34: monsters[monster_index].pMonster = CreateClassPtr((CMXenMaker *)NULL); break;
}
if (monsters[monster_index].pMonster == NULL)
@@ -1435,7 +1437,8 @@ void mmServerActivate( edict_t *pEdictList, int edictCount, int clientMax )
// Extra entities
CMMonsterMaker monstermaker; // 32
CMAmbientMusic ambientmusic;
-
+ CMXenMaker xenmaker;
+
g_psv_gravity = CVAR_GET_POINTER( "sv_gravity" );
(g_engfuncs.pfnAddServerCommand)("monster", MonsterCommand);
@@ -1485,6 +1488,7 @@ void mmServerActivate( edict_t *pEdictList, int edictCount, int clientMax )
case 29: stukabat.Precache(); break;
case 32: monstermaker.Precache(); break;
//case 33: ambientmusic.Precache(); break;
+ case 34: xenmaker.Precache(); break;
}
}
}
diff --git a/src/dlls/monster_config.cpp b/src/dlls/monster_config.cpp
index 5b2950a..cc23d5e 100644
--- a/src/dlls/monster_config.cpp
+++ b/src/dlls/monster_config.cpp
@@ -225,6 +225,22 @@ void scan_monster_cfg(FILE *fp)
monster = TRUE;
}
}
+ else if (strcmp(monster_types[mIndex].name, "env_xenmaker") == 0)
+ {
+ // A monster spawner, add it to the list
+ if (monster_spawn_count == MAX_MONSTERS)
+ {
+ // error.exe
+ LOG_MESSAGE(PLID, "ERROR: can't add monstermaker, reached MAX_MONSTERS!");
+ badent = TRUE;
+ }
+ else
+ {
+ monster_spawnpoint[monster_spawn_count].monster = mIndex;
+ monster_types[mIndex].need_to_precache = TRUE;
+ monster = TRUE;
+ }
+ }
else if (strcmp(monster_types[mIndex].name, "info_node") == 0)
{
// Normal node
@@ -391,8 +407,8 @@ void scan_monster_cfg(FILE *fp)
{
if (monster)
{
- // this keyvalue is only valid for monstermaker entity
- if (strcmp(data[kvd_index-1].value, "monstermaker") == 0 || strcmp(data[kvd_index-1].value, "squadmaker") == 0)
+ // this keyvalue is only valid for monstermaker entities
+ if (strcmp(data[kvd_index - 1].value, "monstermaker") == 0 || strcmp(data[kvd_index - 1].value, "squadmaker") == 0 || strcmp(data[kvd_index - 1].value, "env_xenmaker") == 0)
{
// process the entity precache here
int mIndex;
@@ -771,7 +787,7 @@ void scan_monster_bsp(void)
if (monster)
{
// this keyvalue is only valid for monstermaker entity
- if (strcmp(data[classname_kvdI].value, "monstermaker") == 0 || strcmp(data[classname_kvdI].value, "squadmaker") == 0)
+ if (strcmp(data[classname_kvdI].value, "monstermaker") == 0 || strcmp(data[classname_kvdI].value, "squadmaker") == 0 || strcmp(data[classname_kvdI].value, "env_xenmaker") == 0)
{
// process the entity precache here
for (mIndex = 0; monster_types[mIndex].name[0]; mIndex++)
diff --git a/src/dlls/monster_mm.vcxproj b/src/dlls/monster_mm.vcxproj
index 0d74e15..18deb35 100644
--- a/src/dlls/monster_mm.vcxproj
+++ b/src/dlls/monster_mm.vcxproj
@@ -127,6 +127,7 @@
.\Release/monster_mm.lib
+ true
@@ -186,6 +187,7 @@
+
@@ -224,4 +226,4 @@
-
+
\ No newline at end of file
diff --git a/src/dlls/monster_mm.vcxproj.filters b/src/dlls/monster_mm.vcxproj.filters
index 399fb51..c4a95b0 100644
--- a/src/dlls/monster_mm.vcxproj.filters
+++ b/src/dlls/monster_mm.vcxproj.filters
@@ -186,6 +186,9 @@
Source Files
+
+ Source Files
+
@@ -282,4 +285,4 @@
Header Files
-
+
\ No newline at end of file
diff --git a/src/dlls/xenmaker.cpp b/src/dlls/xenmaker.cpp
new file mode 100644
index 0000000..bd2fd69
--- /dev/null
+++ b/src/dlls/xenmaker.cpp
@@ -0,0 +1,356 @@
+//=========================================================
+// Xen Maker - Sven Co-op's env_xenmaker.
+// Spawns a monster with visual/auditive teleportation effects.
+//=========================================================
+
+#include "extdll.h"
+#include "util.h"
+#include "cmbase.h"
+#include "cmbasemonster.h"
+#include "cmbaseextra.h"
+#include "monsters.h"
+
+// Xenmaker spawnflags
+#define SF_XENMAKER_TRY_ONCE 1 // only one attempt to spawn each time it is fired
+#define SF_XENMAKER_NO_SPAWN 2 // don't spawn anything, only do effects
+
+extern monster_type_t monster_types[];
+extern edict_t* spawn_monster(int monster_type, Vector origin, Vector angles, int spawnflags, pKVD *keyvalue);
+
+
+// ========================================================
+void CMXenMaker::KeyValue(KeyValueData *pkvd)
+{
+ if (FStrEq(pkvd->szKeyName, "monstertype"))
+ {
+ // Process monster_index
+ int mIndex;
+ for (mIndex = 0; monster_types[mIndex].name[0]; mIndex++)
+ {
+ if (strcmp(pkvd->szValue, monster_types[mIndex].name) == 0)
+ {
+ m_iMonsterIndex = mIndex;
+ break; // grab the first entry we find
+ }
+ }
+ if (monster_types[mIndex].name[0] == 0)
+ {
+ ALERT(at_logged, "[MONSTER] XenMaker - %s is not a valid monster type!\n", pkvd->szValue);
+ m_iMonsterIndex = -1;
+ }
+ pkvd->fHandled = TRUE;
+ }
+ else if (FStrEq(pkvd->szKeyName, "m_flBeamRadius"))
+ {
+ m_flBeamRadius = atof(pkvd->szValue);
+ pkvd->fHandled = TRUE;
+ }
+ else if (FStrEq(pkvd->szKeyName, "m_iBeamAlpha"))
+ {
+ m_iBeamAlpha = atoi(pkvd->szValue);
+ pkvd->fHandled = TRUE;
+ }
+ else if (FStrEq(pkvd->szKeyName, "m_iBeamCount"))
+ {
+ m_iBeamCount = atoi(pkvd->szValue);
+ pkvd->fHandled = TRUE;
+ }
+ else if (FStrEq(pkvd->szKeyName, "m_vBeamColor"))
+ {
+ UTIL_StringToVector(m_vBeamColor, pkvd->szValue);
+ pkvd->fHandled = TRUE;
+ }
+ else if (FStrEq(pkvd->szKeyName, "m_flLightRadius"))
+ {
+ m_flLightRadius = atof(pkvd->szValue);
+ pkvd->fHandled = TRUE;
+ }
+ else if (FStrEq(pkvd->szKeyName, "m_vLightColor"))
+ {
+ UTIL_StringToVector(m_vLightColor, pkvd->szValue);
+ pkvd->fHandled = TRUE;
+ }
+ else if (FStrEq(pkvd->szKeyName, "m_flStartSpriteFramerate"))
+ {
+ m_flStartSpriteFramerate = atof(pkvd->szValue);
+ pkvd->fHandled = TRUE;
+ }
+ else if (FStrEq(pkvd->szKeyName, "m_flStartSpriteScale"))
+ {
+ m_flStartSpriteScale = atof(pkvd->szValue);
+ pkvd->fHandled = TRUE;
+ }
+ else if (FStrEq(pkvd->szKeyName, "m_iStartSpriteAlpha"))
+ {
+ m_iStartSpriteAlpha = atoi(pkvd->szValue);
+ pkvd->fHandled = TRUE;
+ }
+ else if (FStrEq(pkvd->szKeyName, "m_vStartSpriteColor"))
+ {
+ UTIL_StringToVector(m_vStartSpriteColor, pkvd->szValue);
+ pkvd->fHandled = TRUE;
+ }
+ else if (FStrEq(pkvd->szKeyName, "m_flEndSpriteFramerate"))
+ {
+ m_flEndSpriteFramerate = atof(pkvd->szValue);
+ pkvd->fHandled = TRUE;
+ }
+ else if (FStrEq(pkvd->szKeyName, "m_flEndSpriteScale"))
+ {
+ m_flEndSpriteScale = atof(pkvd->szValue);
+ pkvd->fHandled = TRUE;
+ }
+ else if (FStrEq(pkvd->szKeyName, "m_iEndSpriteAlpha"))
+ {
+ m_iEndSpriteAlpha = atoi(pkvd->szValue);
+ pkvd->fHandled = TRUE;
+ }
+ else if (FStrEq(pkvd->szKeyName, "m_vEndSpriteColor"))
+ {
+ UTIL_StringToVector(m_vEndSpriteColor, pkvd->szValue);
+ pkvd->fHandled = TRUE;
+ }
+ else
+ CMBaseMonster::KeyValue(pkvd);
+}
+
+
+void CMXenMaker::Spawn()
+{
+ // likely omitted keyvalue, but it could truly be an alien grunt spawn
+ if (m_iMonsterIndex == 0)
+ {
+ if (!monster_types[0].need_to_precache)
+ {
+ // monstertype was not defined, it may be intentional if nothing is to spawn here
+ if (!FBitSet(pev->spawnflags, SF_XENMAKER_NO_SPAWN))
+ ALERT(at_logged, "[MONSTER] Spawned a env_xenmaker entity without a monstertype! targetname: \"%s\"\n", STRING(pev->targetname));
+ m_iMonsterIndex = -1;
+ }
+ }
+
+ pev->solid = SOLID_NOT;
+
+ Precache();
+
+ SetUse(&CMXenMaker::CyclicUse); // drop one monster each time we fire
+ SetThink(&CMXenMaker::SUB_DoNothing);
+
+ m_flGround = 0;
+ pev->classname = MAKE_STRING("env_xenmaker");
+}
+
+void CMXenMaker::Precache(void)
+{
+ m_iBeamIndex = PRECACHE_MODELINDEX("sprites/lgtning.spr");
+ PRECACHE_MODEL("sprites/fexplo1.spr");
+ PRECACHE_MODEL("sprites/xflare1.spr");
+
+ PRECACHE_SOUND("debris/beamstart7.wav");
+ PRECACHE_SOUND("debris/beamstart2.wav");
+
+ CMBaseMonster::Precache();
+ // choosen monster is auto-precached
+}
+
+//=========================================================
+// StartEffect - spawns the monster and starts the effects
+//=========================================================
+void CMXenMaker::StartEffect(void)
+{
+ if (!m_flGround)
+ {
+ // setup altitude
+ TraceResult tr;
+
+ UTIL_TraceLine(pev->origin, pev->origin - Vector(0, 0, 2048), ignore_monsters, ENT(pev), &tr);
+ m_flGround = tr.vecEndPos.z;
+ }
+
+ if (!FBitSet(pev->spawnflags, SF_XENMAKER_NO_SPAWN))
+ {
+ // monstermaker incorrectly setup
+ if (m_iMonsterIndex == -1)
+ {
+ ALERT(at_console, "[MONSTER] NULL Ent in XenMaker!\n");
+ return;
+ }
+
+ edict_t *pent;
+
+ Vector mins = pev->origin - Vector(34, 34, 0);
+ Vector maxs = pev->origin + Vector(34, 34, 0);
+ maxs.z = pev->origin.z;
+ mins.z = m_flGround;
+
+ edict_t *pList[2];
+ int count = UTIL_EntitiesInBox(pList, 2, mins, maxs, FL_CLIENT | FL_MONSTER);
+ if (!count)
+ {
+ // Attempt to spawn monster
+ pent = spawn_monster(m_iMonsterIndex, pev->origin, pev->angles, SF_MONSTER_FALL_TO_GROUND, NULL);
+ if (pent == NULL)
+ {
+ ALERT(at_console, "[MONSTER] XenMaker - failed to spawn monster! targetname: \"%s\"\n", STRING(pev->targetname));
+ }
+ }
+ else if (!FBitSet(pev->spawnflags, SF_XENMAKER_TRY_ONCE))
+ {
+ // wait until spawnpoint is clear
+ pev->nextthink = gpGlobals->time + 1;
+ SetUse(NULL);
+ SetThink(&CMXenMaker::RetryThink);
+ return; // don't do effects
+ }
+ }
+
+ // BEAM EFFECT
+ for (int beam = 0; beam < m_iBeamCount; beam++)
+ {
+ SpawnBeam();
+ }
+
+ // LIGHT EFFECT
+ MESSAGE_BEGIN(MSG_BROADCAST, SVC_TEMPENTITY);
+ WRITE_BYTE(TE_DLIGHT);
+ WRITE_COORD(pev->origin.x);
+ WRITE_COORD(pev->origin.y);
+ WRITE_COORD(pev->origin.z);
+ WRITE_BYTE((int)(m_flLightRadius / 10));
+ WRITE_BYTE((int)m_vLightColor.x);
+ WRITE_BYTE((int)m_vLightColor.y);
+ WRITE_BYTE((int)m_vLightColor.z);
+ WRITE_BYTE(10); // life
+ WRITE_BYTE(0); // decay rate
+ MESSAGE_END();
+
+ // SPRITE EFFECT
+ CMSprite *pSprite = CMSprite::SpriteCreate("sprites/fexplo1.spr", pev->origin, FALSE);
+ if (pSprite)
+ {
+ pSprite->SetScale(m_flStartSpriteScale);
+ pSprite->SetTransparency(kRenderGlow, (int)m_vStartSpriteColor.x, (int)m_vStartSpriteColor.y, (int)m_vStartSpriteColor.z, m_iStartSpriteAlpha, kRenderFxNoDissipation);
+ pSprite->AnimateAndDie(m_flStartSpriteFramerate);
+ }
+
+ // SOUND EFFECT
+ EMIT_SOUND_DYN(ENT(pev), CHAN_AUTO, "debris/beamstart7.wav", VOL_NORM, ATTN_NORM, 0, PITCH_NORM);
+
+ pev->nextthink = gpGlobals->time + 0.5;
+ SetUse(NULL);
+ SetThink(&CMXenMaker::MiddleEffect);
+}
+
+//=========================================================
+// MiddleEffect - second set of effects
+//=========================================================
+void CMXenMaker::MiddleEffect(void)
+{
+ // SPRITE EFFECT
+ CMSprite *pSprite = CMSprite::SpriteCreate("sprites/xflare1.spr", pev->origin, FALSE);
+ if (pSprite)
+ {
+ pSprite->SetScale(m_flEndSpriteScale);
+ pSprite->SetTransparency(kRenderGlow, (int)m_vEndSpriteColor.x, (int)m_vEndSpriteColor.y, (int)m_vEndSpriteColor.z, m_iEndSpriteAlpha, kRenderFxNoDissipation);
+ pSprite->AnimateAndDie(m_flEndSpriteFramerate);
+ }
+
+ pev->nextthink = gpGlobals->time + 0.5;
+ SetThink(&CMXenMaker::EndEffect);
+}
+
+//=========================================================
+// EndEffect - final set of effects
+//=========================================================
+void CMXenMaker::EndEffect(void)
+{
+ // SOUND EFFECT
+ EMIT_SOUND_DYN(ENT(pev), CHAN_AUTO, "debris/beamstart2.wav", VOL_NORM, ATTN_NORM, 0, PITCH_NORM);
+
+ SetUse(&CMXenMaker::CyclicUse);
+ SetThink(&CMXenMaker::SUB_DoNothing);
+}
+
+//=========================================================
+// CyclicUse - drops one monster from the xen maker
+// each time we call this.
+//=========================================================
+void CMXenMaker::CyclicUse(edict_t *pActivator, edict_t *pCaller, USE_TYPE useType, float value)
+{
+ StartEffect();
+}
+
+//=========================================================
+// RetryThink - try spawning again if spawn was obstructed
+//=========================================================
+void CMXenMaker::RetryThink(void)
+{
+ SetUse(&CMXenMaker::CyclicUse);
+ SetThink(&CMXenMaker::SUB_DoNothing);
+
+ StartEffect();
+}
+
+//=========================================================
+// SpawnBeam - calculates beam end position and creates it.
+// starting position is the origin of the xenmaker itself.
+//=========================================================
+void CMXenMaker::SpawnBeam(void)
+{
+ // CLightning::RandomArea
+ for (int iLoops = 0; iLoops < 10; iLoops++)
+ {
+ Vector vecSrc = pev->origin;
+
+ Vector vecDir1 = Vector(RANDOM_FLOAT(-1.0, 1.0), RANDOM_FLOAT(-1.0, 1.0), RANDOM_FLOAT(-1.0, 1.0));
+ vecDir1 = vecDir1.Normalize();
+ TraceResult tr1;
+ UTIL_TraceLine(vecSrc, vecSrc + vecDir1 * m_flBeamRadius, ignore_monsters, ENT(pev), &tr1);
+
+ if (tr1.flFraction == 1.0)
+ continue;
+
+ Vector vecDir2;
+ do
+ {
+ vecDir2 = Vector(RANDOM_FLOAT(-1.0, 1.0), RANDOM_FLOAT(-1.0, 1.0), RANDOM_FLOAT(-1.0, 1.0));
+ } while (DotProduct(vecDir1, vecDir2) > 0);
+ vecDir2 = vecDir2.Normalize();
+ TraceResult tr2;
+ UTIL_TraceLine(vecSrc, vecSrc + vecDir2 * m_flBeamRadius, ignore_monsters, ENT(pev), &tr2);
+
+ if (tr2.flFraction == 1.0)
+ continue;
+
+ if ((tr1.vecEndPos - tr2.vecEndPos).Length() < m_flBeamRadius * 0.1)
+ continue;
+
+ UTIL_TraceLine(tr1.vecEndPos, tr2.vecEndPos, ignore_monsters, ENT(pev), &tr2);
+
+ if (tr2.flFraction != 1.0)
+ continue;
+
+ // CLightning::Zap
+ MESSAGE_BEGIN(MSG_BROADCAST, SVC_TEMPENTITY);
+ WRITE_BYTE(TE_BEAMPOINTS);
+ WRITE_COORD(tr1.vecEndPos.x);
+ WRITE_COORD(tr1.vecEndPos.y);
+ WRITE_COORD(tr1.vecEndPos.z);
+ WRITE_COORD(tr2.vecEndPos.x);
+ WRITE_COORD(tr2.vecEndPos.y);
+ WRITE_COORD(tr2.vecEndPos.z);
+ WRITE_SHORT(m_iBeamIndex);
+ WRITE_BYTE(0); // starting frame
+ WRITE_BYTE(10); // framerate
+ WRITE_BYTE(10); // life
+ WRITE_BYTE(16); // width
+ WRITE_BYTE(64); // noise
+ WRITE_BYTE((int)m_vBeamColor.x);
+ WRITE_BYTE((int)m_vBeamColor.y);
+ WRITE_BYTE((int)m_vBeamColor.z);
+ WRITE_BYTE(m_iBeamAlpha);
+ WRITE_BYTE(15); // speed
+ MESSAGE_END();
+ break;
+ }
+}