| Name | Author | Game Mode | Rating | |||||
|---|---|---|---|---|---|---|---|---|
| NpcBunnies | Cranky | Mutator | N/A | |||||
namespace npcBunniesFighting
{
array<bool> npcPlayerFireWasDown(32, false);
array<int> npcPlayerLastFireTick(32, -9999);
array<int> npcSimpleLastPlayerFireTick;
bool npcFightingEnabledCacheValid = false;
bool npcFightingEnabledCache = false;
void MarkNpcFightingStateDirty()
{
npcFightingEnabledCacheValid = false;
}
bool HasAnyNpcFightingEnabled()
{
if(npcFightingEnabledCacheValid) return npcFightingEnabledCache;
npcFightingEnabledCache = false;
for(uint i = 0; i < npcBunniesCore::npcBunnies.length; i++)
{
if(npcBunniesCore::npcBunnies[i].fightingAvailable && npcBunniesCore::npcBunnies[i].fightingEnabled)
{
npcFightingEnabledCache = true;
break;
}
}
npcFightingEnabledCacheValid = true;
return npcFightingEnabledCache;
}
int ParseNpcFightingStyle(const string &in value)
{
if(jjRegexMatch(value, "Simple", true)) return npcBunniesCore::NpcFightingStyle::Simple;
return npcBunniesCore::NpcFightingStyle::Complex;
}
string NpcFightingStyleName(int style)
{
return style == npcBunniesCore::NpcFightingStyle::Simple ? "Simple" : "Complex";
}
void ParseNpcFighting(array<string>@ tokens, array<int>@ pos, npcBunniesCore::npcBunny &inout npc)
{
if(pos[0] >= int(tokens.length) || tokens[pos[0]] != "{")
{
npc.fightingAvailable = npcBunniesParser::ReadNpcJsonBool(tokens, pos);
npc.fightingEnabled = npc.fightingAvailable;
return;
}
pos[0]++;
while(pos[0] < int(tokens.length) && tokens[pos[0]] != "}")
{
string key = tokens[pos[0]++]; if(pos[0] < int(tokens.length) && tokens[pos[0]] == ":") pos[0]++;
if(jjRegexMatch(key, "Enabled", true) || jjRegexMatch(key, "On", true))
{
npc.fightingAvailable = npcBunniesParser::ReadNpcJsonBool(tokens, pos);
npc.fightingEnabled = npc.fightingAvailable;
}
else if(jjRegexMatch(key, "Style", true) || jjRegexMatch(key, "FightingStyle", true)) npc.fightingStyle = ParseNpcFightingStyle(npcBunniesParser::ReadNpcJsonString(tokens, pos));
else npcBunniesParser::SkipNpcJsonValue(tokens, pos);
if(pos[0] < int(tokens.length) && tokens[pos[0]] == ",") pos[0]++;
}
if(pos[0] < int(tokens.length) && tokens[pos[0]] == "}") pos[0]++;
}
void UpdateNpcFightingPlayerFireState()
{
for(int i = 0; i < 32; i++)
{
bool fireDown = jjPlayers[i].isInGame && jjPlayers[i].keyFire;
if(fireDown && !npcPlayerFireWasDown[i]) npcPlayerLastFireTick[i] = jjGameTicks;
npcPlayerFireWasDown[i] = fireDown;
}
}
bool IsNpcFightingTargetEventID(int eventID)
{
switch(eventID)
{
case OBJECT::BAT: case OBJECT::BEE: case OBJECT::BEEBOY: case OBJECT::BEES: case OBJECT::BUTTERFLY:
case OBJECT::CRAB: case OBJECT::DEMON: case OBJECT::DOGGYDOGG: case OBJECT::DRAGON: case OBJECT::DRAGONFLY:
case OBJECT::FATCHICK: case OBJECT::FENCER: case OBJECT::FISH: case OBJECT::HATTER: case OBJECT::HELMUT:
case OBJECT::LABRAT: case OBJECT::LIZARD: case OBJECT::FLOATLIZARD: case OBJECT::XMASLIZARD: case OBJECT::MONKEY:
case OBJECT::STANDMONKEY: case OBJECT::NORMTURTLE: case OBJECT::XMASNORMTURTLE: case OBJECT::TUBETURTLE: case OBJECT::TUFTURT:
case OBJECT::RAPIER: case OBJECT::RAVEN: case OBJECT::SKELETON: case OBJECT::SPARK: case OBJECT::SUCKER:
case OBJECT::FLOATSUCKER: case OBJECT::CAT: case OBJECT::PACMANGHOST: case OBJECT::MOTH: case OBJECT::WITCH:
case OBJECT::BILSY: case OBJECT::XMASBILSY: case OBJECT::BOLLY: case OBJECT::BUBBA: case OBJECT::DEVANROBOT:
case OBJECT::DEVILDEVAN: case OBJECT::QUEEN: case OBJECT::ROBOT: case OBJECT::ROCKETTURTLE: case OBJECT::TUFBOSS:
case OBJECT::TWEEDLEBOSS: case OBJECT::UTERUS:
return true;
}
return false;
}
bool NpcHasLineOfSight(float x1, float y1, float x2, float y2)
{
float dx = x2 - x1;
float dy = y2 - y1;
int steps = int((abs(dx) > abs(dy) ? abs(dx) : abs(dy)) / 16);
if(steps < 1) steps = 1;
for(int i = 1; i < steps; i++)
{
float t = float(i) / float(steps);
if(jjMaskedPixel(int(x1 + dx * t), int(y1 + dy * t))) return false;
}
return true;
}
int FindNpcComplexFightingTarget(jjOBJ@ npcObj)
{
if(npcObj is null) return -1;
int rangeSquared = npcBunniesCore::NPC_FIGHT_SIGHT_RANGE * npcBunniesCore::NPC_FIGHT_SIGHT_RANGE;
int bestId = -1;
int bestDistance = rangeSquared + 1;
for(int i = 1; i < jjObjectCount; i++)
{
jjOBJ@ target = jjObjects[i];
if(!target.isActive || target.energy <= 0) continue;
if(!IsNpcFightingTargetEventID(target.eventID)) continue;
float dx = target.xPos - npcObj.xPos;
float dy = target.yPos - npcObj.yPos;
if(abs(dx) > npcBunniesCore::NPC_FIGHT_SIGHT_RANGE || abs(dy) > npcBunniesCore::NPC_FIGHT_SIGHT_RANGE) continue;
int distanceSquared = int(dx * dx + dy * dy);
if(distanceSquared >= bestDistance || distanceSquared > rangeSquared) continue;
if(!NpcHasLineOfSight(npcObj.xPos, npcObj.yPos - 12, target.xPos, target.yPos - 12)) continue;
bestId = target.objectID;
bestDistance = distanceSquared;
}
return bestId;
}
bool NpcCanShootHorizontallyAt(jjOBJ@ npcObj, jjOBJ@ target)
{
if(npcObj is null || target is null) return false;
if(abs((target.yPos - 8) - (npcObj.yPos - 8)) > npcBunniesCore::NPC_FIGHT_HORIZONTAL_Y_TOLERANCE) return false;
return NpcHasLineOfSight(npcObj.xPos, npcObj.yPos - 8, target.xPos, npcObj.yPos - 8);
}
void NpcFireBlasterAt(npcBunniesCore::npcBunny &inout npc, jjOBJ@ npcObj, float targetX, float targetY)
{
if(npcObj is null) return;
float dx = targetX - npcObj.xPos;
if(dx == 0) return;
float speed = 7.0;
int creatorId = (0 <= npc.playerFollowing && npc.playerFollowing < 32) ? npc.playerFollowing : 0;
int bulletId = jjAddObject(OBJECT::BLASTERBULLET, npcObj.xPos + (dx >= 0 ? 18 : -18), npcObj.yPos - 8, creatorId, CREATOR::PLAYER, BEHAVIOR::DEFAULT);
if(bulletId <= 0) return;
jjOBJ@ bullet = jjObjects[bulletId];
bullet.xSpeed = dx > 0 ? speed : -speed;
bullet.ySpeed = 0;
bullet.direction = dx > 0 ? 1 : -1;
npcObj.direction = bullet.direction;
npc.direction = npcObj.direction;
npc.fightingDirection = npcObj.direction;
if(npc.id >= 0 && npcBunniesCore::ENABLE_NPC_SYNC) npcBunniesHooks::sendNpcSync(npc.id, npc.playerFollowing, npc.followingTrainIndex, 0);
npc.fightingLastFireTick = jjGameTicks;
npc.fightingAnimUntilTick = jjGameTicks + npcBunniesCore::NPC_FIGHT_ANIMATION_TICKS;
}
void UpdateNpcComplexFighting(npcBunniesCore::npcBunny &inout npc, jjOBJ@ obj)
{
if(npc.fightingLastScanTick + npcBunniesCore::NPC_FIGHT_COMPLEX_SCAN_INTERVAL <= jjGameTicks)
{
npc.fightingLastScanTick = jjGameTicks + (npc.id % 17);
npc.fightingTargetObjectID = FindNpcComplexFightingTarget(obj);
}
if(npc.fightingTargetObjectID <= 0 || npc.fightingLastFireTick + npcBunniesCore::NPC_FIGHT_FIRE_INTERVAL > jjGameTicks) return;
jjOBJ@ target = jjObjects[npc.fightingTargetObjectID];
if(!target.isActive || target.energy <= 0 || !IsNpcFightingTargetEventID(target.eventID)) { npc.fightingTargetObjectID = -1; return; }
if(!NpcCanShootHorizontallyAt(obj, target)) return;
NpcFireBlasterAt(npc, obj, target.xPos, obj.yPos - 8);
}
void UpdateNpcSimpleFighting(npcBunniesCore::npcBunny &inout npc, jjOBJ@ obj)
{
if(npc.id < 0 || npc.id >= int(npcSimpleLastPlayerFireTick.length)) return;
if(npc.playerFollowing < 0 || npc.playerFollowing >= 32) return;
int playerFireTick = npcPlayerLastFireTick[npc.playerFollowing];
if(playerFireTick <= npcSimpleLastPlayerFireTick[npc.id]) return;
int delay = 35 + (npc.id * 11) % 36;
if(jjGameTicks < playerFireTick + delay) return;
if(npc.fightingLastFireTick + npcBunniesCore::NPC_FIGHT_FIRE_INTERVAL > jjGameTicks) return;
jjPLAYER@ player = jjPlayers[npc.playerFollowing];
float targetX = obj.xPos + (player.direction >= 0 ? 320 : -320);
float targetY = obj.yPos - 8;
NpcFireBlasterAt(npc, obj, targetX, targetY);
npcSimpleLastPlayerFireTick[npc.id] = playerFireTick;
}
void UpdateNpcFighting(npcBunniesCore::npcBunny &inout npc, jjOBJ@ obj)
{
if(!jjIsServer && npcBunniesCore::ENABLE_NPC_SYNC && !npcBunniesCore::CLIENT_SIMULATES_NPC_MOVEMENT) return;
if(npcBunniesAnimations::IsNpcStaticType(npc.type) || !npc.fightingAvailable || !npc.fightingEnabled) return;
if(npc.playerFollowing < 0 || npc.guideActive) return;
if(npc.fightingStyle == npcBunniesCore::NpcFightingStyle::Simple) UpdateNpcSimpleFighting(npc, obj);
else UpdateNpcComplexFighting(npc, obj);
}
void ApplyNpcFightingSync(int id, bool enabled)
{
if(id < 0 || id >= int(npcBunniesCore::npcBunnies.length) || !npcBunniesCore::npcBunnies[id].fightingAvailable) return;
npcBunniesCore::npcBunnies[id].fightingEnabled = enabled;
npcBunniesCore::npcBunnies[id].fightingTargetObjectID = -1;
MarkNpcFightingStateDirty();
}
void SendNpcFightingSync(int id, int clientID = 0)
{
if(!npcBunniesCore::ENABLE_NPC_SYNC) return;
if(id < 0 || id >= int(npcBunniesCore::npcBunnies.length)) return;
jjSTREAM packet;
packet.push("npcFight " + id + " " + (npcBunniesCore::npcBunnies[id].fightingEnabled ? 1 : 0));
if(clientID == 0) jjSendPacket(packet);
else jjSendPacket(packet, clientID);
}
//----------------------------------------------------------------------------------------------------
// NPC MONSTER / BOSS / STATIC TYPE REGION
}
Jazz2Online © 1999-INFINITY (Site Credits). We have a Privacy Policy. Jazz Jackrabbit, Jazz Jackrabbit 2, Jazz Jackrabbit Advance and all related trademarks and media are ™ and © Epic Games. Lori Jackrabbit is © Dean Dodrill. J2O development powered by Loops of Fury and Chemical Beats.
Eat your lima beans, Johnny.