| Name | Author | Game Mode | Rating | |||||
|---|---|---|---|---|---|---|---|---|
| Multiflag |
Violet CLM | Mutator | 9.5 | |||||
#pragma name "Multiflag"
#pragma description "Variant on Capture the Flag. Flags have no inherent team: bring one from the enemy base to your base to score a point, and it will stay at your base until recaptured. Roast an enemy flagholder to steal their flag, unless you're holding a flag already. Use the !flagsperteam command to add even more flags to the game."
#include "ArtificialArrow.asc"
enum SpecialPlayerID { Free = -1, BlueBase = -2, RedBase = -3 }
class Flag {
float xPos, yPos;
int playerID;
int direction;
int timeout;
Flag(){}
}
array<Flag> Flags(32);
uint FlagCount = 2;
const int FreeFlagTimeout = 30 * 70;
const bool LimitAllFlagPoints = true;
class Point {
int xPos, yPos;
Point(){}
Point(int x, int y) { xPos = x * 32 + 15; yPos = y * 32 + 15; }
}
array<Point> BaseLocations(2);
array<int> BaseDirections(2);
array<jjOBJ@> BaseObjects(2, null);
array<int> NextCaptureTime(32, 5*70);
jjANIMSET@ CustomSet;
void onLevelLoad() {
{
//3, 4: blue
//7, 8: red
//ingame first, HUD second
uint8 customSetID = 0;
jjANIMSET@ flagSet = jjAnimSets[ANIM::FLAG];
while (jjAnimSets[ANIM::CUSTOM[customSetID]] != 0) { ++customSetID; }
@CustomSet = jjAnimSets[ANIM::CUSTOM[customSetID]];
CustomSet.allocate(array<uint>={8,0,0,0,0});
jjPIXELMAP emptySprite(1,1);
for (uint i = 0; i < 8; ++i)
emptySprite.save(jjAnimFrames[jjAnimations[CustomSet]+i]);
jjAnimations[CustomSet+1] = jjAnimations[flagSet+3];
jjAnimations[CustomSet+2] = jjAnimations[flagSet+7];
jjAnimations[CustomSet+3] = jjAnimations[flagSet+4];
jjAnimations[CustomSet+4] = jjAnimations[flagSet+8];
jjAnimations[flagSet+3] = jjAnimations[flagSet+4] = jjAnimations[flagSet+7] = jjAnimations[flagSet+8] = jjAnimations[CustomSet];
}
uint levelAlreadyHasCTFBases = 0;
for (int x = jjLayerWidth[4]; --x >= 0;)
for (int y = jjLayerHeight[4]; --y >= 0;) {
const auto eventID = jjEventGet(x,y);
if (eventID == OBJECT::CTFBASE) {
const uint teamID = jjParameterGet(x,y,0,1);
BaseLocations[teamID] = Point(x,y);
BaseDirections[teamID] = (jjParameterGet(x,y,1,1) * 2) - 1;
levelAlreadyHasCTFBases |= (teamID + 1);
}
}
if (levelAlreadyHasCTFBases != 3) { //0b11
jjConsole("|WARNING: level does not have both CTF bases.");
}
if (jjIsServer) {
jjSTREAM load("multiflag.asdat");
if (!load.isEmpty()) {
load.pop(FlagCount);
}
}
for (uint i = 0; i < Flags.length; ++i) { //start
const int teamID = int(i & 1);
Flags[i].playerID = SpecialPlayerID::BlueBase - int(teamID);
}
}
void onLevelBegin() {
if (jjIsServer) {
if (jjGameCustom != GAME::FR)
jjChat("/fr");
} else
jjSendPacket(jjSTREAM());
for (uint i = jjObjectCount; --i != 0;) {
jjOBJ@ obj = jjObjects[i];
if (obj.behavior == BEHAVIOR::FLAG)
@BaseObjects[jjParameterGet(int(obj.xOrg) >> 5, int(obj.yOrg) >> 5, 0, 1)] = @obj;
}
}
void onPlayer(jjPLAYER@ player) {
array<int> flagLocations;
uint8 spriteParam = player.teamRed ? 32 : 24;
if (DoesPlayerHaveFlag(player) !is null) {
flagLocations = array<int> = { BaseLocations[player.team].xPos, BaseLocations[player.team].yPos };
spriteParam ^= (32+24);
} else {
for (uint flagID = 0; flagID < FlagCount; ++flagID) {
const Flag@ flag = Flags[flagID];
if (flag.playerID == SpecialPlayerID::Free || flag.playerID == (player.teamRed ? SpecialPlayerID::BlueBase : SpecialPlayerID::RedBase) || (flag.playerID >= 0 && jjPlayers[flag.playerID].teamRed != player.teamRed)) {
flagLocations.insertLast(int(flag.xPos));
flagLocations.insertLast(int(flag.yPos));
}
}
}
ArtificialArrow::DrawArrow(flagLocations, player, SPRITE::SINGLEHUE, spriteParam);
}
void AssignFlagToBase(Flag@ flag, int teamID, bool score = true) {
flag.playerID = SpecialPlayerID::BlueBase - teamID;
const Point@ homeBase = BaseLocations[teamID];
if (score) {
if (teamID == 1)
jjChat("/redscore " + (jjTeamScore[TEAM::RED] + 1));
else
jjChat("/bluescore " + (jjTeamScore[TEAM::BLUE] + 1));
}
}
void onRoast(jjPLAYER@ victim, jjPLAYER@ killer) {
if (jjIsServer) {
NextCaptureTime[victim.playerID] = jjGameTicks + 7*70;
Flag@ flag = DoesPlayerHaveFlag(victim);
if (flag !is null) {
if (victim !is killer) { //roast
if (DoesPlayerHaveFlag(killer) is null) {
flag.playerID = killer.playerID;
if (!killer.teamRed)
jjAlert("||" + killer.nameUnformatted + " ||||stole a flag from |||" + victim.nameUnformatted, true, STRING::MEDIUM);
else
jjAlert("|" + killer.nameUnformatted + " |||||stole a flag from ||||" + victim.nameUnformatted, true, STRING::MEDIUM);
} else {
flag.playerID = SpecialPlayerID::Free;
flag.timeout = jjGameTicks + FreeFlagTimeout;
}
} else { //pit death
AssignFlagToBase(flag, victim.team ^ 1, false);
}
SendFlagData();
}
}
}
void onReceive(jjSTREAM &in packet, int clientID) {
if (packet.isEmpty()) { //ping
if (jjIsServer)
SendFlagData(clientID);
else
AutoScoreCountdown = 5*70;
} else { //flag data
uint8 fc;
packet.pop(fc);
FlagCount = fc;
for (uint flagID = 0; flagID < FlagCount; ++flagID) {
Flag@ flag = Flags[flagID];
int8 pid;
packet.pop(pid);
flag.playerID = pid;
if (pid == SpecialPlayerID::Free) {
int16 pos;
packet.pop(pos);
flag.xPos = pos;
packet.pop(pos);
flag.yPos = pos;
flag.timeout = jjGameTicks + FreeFlagTimeout; //approximate
}
}
}
}
array<uint> FlagsPerTeam(2);
uint AutoScoreCountdown = 0;
bool someTeamHasAllFlags = false;
array<int> LastTeamScore(2, 0);
void onMain() {
someTeamHasAllFlags = false;
FlagsPerTeam[0] = FlagsPerTeam[1] = 0;
const bool gameActive = (jjGameState == GAME::STARTED || jjGameState == GAME::OVERTIME);
array<int> baseFlagAngles(2, 0);
for (uint flagID = 0; flagID < FlagCount; ++flagID) {
Flag@ flag = Flags[flagID];
if (flag.playerID >= 0) {
const jjPLAYER@ holder = jjPlayers[flag.playerID];
flag.xPos = holder.xPos;
flag.yPos = holder.yPos;
flag.direction = -holder.direction;
} else if (flag.playerID != SpecialPlayerID::Free) {
uint teamID = flag.playerID == SpecialPlayerID::BlueBase ? 0 : 1;
flag.xPos = BaseLocations[teamID].xPos;
flag.yPos = BaseLocations[teamID].yPos;
flag.direction = BaseDirections[teamID];
}
if (jjIsServer && gameActive) {
if (flag.playerID >= 0) {
const jjPLAYER@ holder = jjPlayers[flag.playerID];
if (holder.health <= 0 || !holder.isInGame) {
flag.playerID = (jjTeamScore[TEAM::BLUE] > jjTeamScore[TEAM::RED]) ? SpecialPlayerID::BlueBase : SpecialPlayerID::RedBase;
SendFlagData();
} else {
const auto teamID = holder.team;
const Point@ homeBase = BaseLocations[teamID];
if (abs(flag.xPos - homeBase.xPos) <= 32 && abs(flag.yPos - homeBase.yPos) <= 32) {
const string pointScore = (teamID == 1 ? "|" : "||") + holder.nameUnformatted + " scored the point";
AssignFlagToBase(flag, teamID);
jjAlert(pointScore, true);
SendFlagData();
}
}
} else {
if (flag.playerID == SpecialPlayerID::Free && jjGameTicks >= flag.timeout) {
flag.playerID = (jjTeamScore[TEAM::BLUE] > jjTeamScore[TEAM::RED]) ? SpecialPlayerID::BlueBase : SpecialPlayerID::RedBase;
SendFlagData();
jjAlert("A flag timed out and returned to the " + (flag.playerID == SpecialPlayerID::BlueBase ? "blue" : "red") + " base", true);
} else for (uint playerID = 0; playerID < 32; ++playerID) {
const jjPLAYER@ player = jjPlayers[playerID];
if (player.isInGame && NextCaptureTime[playerID] < jjGameTicks && (flag.playerID == SpecialPlayerID::Free || ((flag.playerID == SpecialPlayerID::BlueBase) == (player.teamRed))) && DoesPlayerHaveFlag(player) is null) {
if (abs(flag.xPos - player.xPos) <= 32 && abs(flag.yPos - player.yPos) <= 32) {
flag.playerID = playerID;
jjAlert((player.teamRed ? "|" : "||") + player.nameUnformatted + " captured a flag", true, STRING::MEDIUM);
NextCaptureTime[playerID] = jjGameTicks + 3*70; //why not
SendFlagData();
break; //don't be captured twice at once
}
}
}
}
}
if (flag.playerID >= SpecialPlayerID::Free) {
jjDrawSpriteFromCurFrame(flag.xPos, flag.yPos+6, jjAnimations[CustomSet.firstAnim + (flag.playerID == SpecialPlayerID::BlueBase ? 1 : 2)].firstFrame + ((jjGameTicks >> 3) & 7), flag.direction, SPRITE::SINGLEHUE, 64);
if (flag.playerID == SpecialPlayerID::Free) {
jjDrawSpriteFromCurFrame(flag.xPos - 16, flag.yPos - 20, jjObjectPresets[OBJECT::STOPWATCH].curFrame + ((jjGameTicks >> 3) & 7), -1);
jjDrawString(flag.xPos, flag.yPos - 20, "" + ((flag.timeout - jjGameTicks) / 70 + 1), STRING::MEDIUM);
}
} else {
const int teamID = flag.playerID == SpecialPlayerID::BlueBase ? 0 : 1;
jjDrawRotatedSpriteFromCurFrame(flag.xPos, flag.yPos+21, jjAnimations[CustomSet.firstAnim + teamID + 1].firstFrame + (((jjGameTicks >> 3) + flagID) & 7), flag.direction * baseFlagAngles[teamID], flag.direction,1, SPRITE::NORMAL);
baseFlagAngles[teamID] -= 32;
FlagsPerTeam[teamID] += 1;
if (FlagsPerTeam[teamID] == FlagCount) {
if ((!LimitAllFlagPoints) || (jjTeamScore[TEAM::Color(teamID)] <= jjTeamScore[TEAM::Color(teamID ^ 1)])) //don't let a team get too big of a lead
someTeamHasAllFlags = true;
}
}
}
if (someTeamHasAllFlags && gameActive) {
if (AutoScoreCountdown != 0) {
AutoScoreCountdown -= 1;
if (jjIsServer && AutoScoreCountdown == 0) {
jjSamplePriority(SOUND::COMMON_BELL_FIRE2);
if (FlagsPerTeam[0] == 0)
jjChat("/redscore " + (jjTeamScore[TEAM::RED] + 1));
else
jjChat("/bluescore " + (jjTeamScore[TEAM::BLUE] + 1));
AutoScoreCountdown = 5*70;
SendTimePacket();
}
}
if (jjIsServer) {
if (AutoScoreCountdown == 0) {
AutoScoreCountdown = 5*70;
jjSamplePriority(SOUND::COMMON_BELL_FIRE2);
jjAlert((FlagsPerTeam[0] == 0 ? "|Red" : "||Blue") + " team has all flags", true, STRING::MEDIUM);
SendTimePacket();
}
}
} else
AutoScoreCountdown = 0;
for (int teamID = 0; teamID < 2; ++teamID) {
if (jjTeamScore[TEAM::Color(teamID)] != LastTeamScore[teamID]) {
if (jjTeamScore[TEAM::Color(teamID)] > LastTeamScore[teamID] && BaseObjects[teamID] !is null) {
BaseObjects[teamID].var[4] = 1; //make eva jump
BaseObjects[teamID].counter = 0; //
}
LastTeamScore[teamID] = jjTeamScore[TEAM::Color(teamID)];
}
}
for (uint playerID = 0; playerID < 32; ++playerID) {
jjPLAYER@ player = jjPlayers[playerID];
player.iconSet((player.isInGame && DoesPlayerHaveFlag(player) !is null) ? FlagIcon : null);
}
}
jjANIMFRAME@ FlagIcon = jjAnimFrames[jjAnimations[jjAnimSets[ANIM::PLUS_PLAYERSTATEICONS]] + 1];
Flag@ DoesPlayerHaveFlag(const jjPLAYER@ player) {
const int playerID = player.playerID;
bool localPlayerHasFlag = false;
for (uint flagID = 0; flagID < FlagCount; ++flagID) {
Flag@ flag = Flags[flagID];
if (flag.playerID == playerID)
return flag;
}
return null;
}
void onChat(int clientID, string &in stringReceived, CHAT::Type chatType) {
if (chatType == CHAT::TEAMCHAT && jjRegexMatch(stringReceived, "h\\??", true)) {
if (DoesPlayerHaveFlag(jjLocalPlayers[0]) !is null)
jjChat("" + jjLocalPlayers[0].health, true);
} else if (jjIsServer && (clientID == 0 || jjPlayersWithClientID(clientID)[0].isAdmin)) {
array<string> results;
if (jjRegexMatch(stringReceived, "!flagsperteam\\s+(\\d\\d?)", results, true)) {
uint proposedFlagCount = parseUInt(results[1]);
if (proposedFlagCount >= 1 && proposedFlagCount <= Flags.length/2) {
proposedFlagCount *= 2;
if (proposedFlagCount != FlagCount) {
for (uint lostFlag = FlagCount - 1; lostFlag >= proposedFlagCount; --lostFlag) {
Flag@ flag = Flags[lostFlag];
if (flag.playerID >= 0)
jjConsole(jjPlayers[flag.playerID].name + "'s flag is gone.");
}
FlagCount = proposedFlagCount;
jjSTREAM save;
save.push(FlagCount);
save.save("multiflag.asdat");
SendFlagData();
jjConsole("Flags Per Team has been set to " + (proposedFlagCount/2), true);
} else {
jjConsole("Flags Per Team is already " + (proposedFlagCount/2), true);
}
} else
jjConsole("|Must be between 1 and " + (Flags.length/2) + " flags per team.", true);
}
}
}
bool onDrawGameModeHUD(jjPLAYER@, jjCANVAS@ canvas) {
array<uint> evenHeldFlags = FlagsPerTeam;
const bool wide = jjResolutionWidth > 400;
const int x = wide ? 100 : 80;
int y = wide ? 22 : 17;
uint teamWithAllFlags = 99;
if (someTeamHasAllFlags) {
const bool redHasFlags = FlagsPerTeam[0] == 0;
if ((!LimitAllFlagPoints) || (jjTeamScore[redHasFlags ? TEAM::RED : TEAM::BLUE] <= jjTeamScore[redHasFlags ? TEAM::BLUE : TEAM::RED])) {
const int stringCenter = 0x8000 - (jjSubscreenWidth / 2) + 30;
canvas.drawString(stringCenter, 60, (redHasFlags ? "||" : "|||") + ((AutoScoreCountdown + 69) / 70), STRING::LARGE);
teamWithAllFlags = redHasFlags ? 1 : 0;
}
}
if (((jjRenderFrame/17)&1) == 1) {
for (uint flagID = 0; flagID < FlagCount; ++flagID) {
const Flag@ flag = Flags[flagID];
if (flag.playerID >= 0)
evenHeldFlags[jjPlayers[flag.playerID].teamRed ? 1 : 0] += 1;
}
}
for (uint teamID = 0; teamID < 2; ++teamID) {
const auto firstFrame = jjAnimations[CustomSet + 3 + teamID].firstFrame;
int xx = x, yy = y;
if (teamWithAllFlags == teamID) {
xx += jjRandom() & 3;
yy += jjRandom() & 3;
}
for (uint flagID = 0; flagID < evenHeldFlags[teamID]; ++flagID)
canvas.drawSpriteFromCurFrame(xx + flagID * 20, yy, firstFrame + (((jjGameTicks >> 3) + flagID) & 7), 1, flagID < FlagsPerTeam[teamID] ? SPRITE::NORMAL : SPRITE::SINGLEHUE, 64);
/*
for (uint flagID = 0; flagID < FlagCount; ++flagID) {
const Flag@ flag = Flags[flagID];
if (flag.playerID == SpecialPlayerID::Free || flag.playerID == (player.teamRed ? SpecialPlayerID::BlueBase : SpecialPlayerID::RedBase) || (flag.playerID >= 0 && jjPlayers[flag.playerID].teamRed != player.teamRed)) {*/
y += wide ? 24 : 12;
}
return false;
}
void SendFlagData(int clientID = 0) {
//there's little enough that it's easier on my poor tender head to send everything at once every dang time
jjSTREAM packet;
packet.push(uint8(FlagCount));
for (uint flagID = 0; flagID < FlagCount; ++flagID) {
const Flag@ flag = Flags[flagID];
packet.push(int8(flag.playerID));
if (flag.playerID == SpecialPlayerID::Free) {
packet.push(int16(flag.xPos));
packet.push(int16(flag.yPos));
}
}
jjSendPacket(packet, clientID);
}
void SendTimePacket() {
jjSendPacket(jjSTREAM());
}
void onHelp(array<jjHELPPAGE@>& pages) {
for (int i = pages.length - 1; i >= 0; --i) { //remove any existing pages with the gamemode tag
if (jjRegexSearch(pages[i].tags, "\\bgamemode\\b", true))
pages.removeAt(i);
}
pages.insertLast(jjHELPPAGE(
{ //beginning of array of jjHELPPARAGRAPH instances
jjHELPPARAGRAPH(
"||Multi|flag",
textSize: STRING::MEDIUM,
textAlignment: STRING::CENTER
),
jjHELPPARAGRAPH("When you bring a flag to your base, you score a point even if theres no flag there. The flag you brought then stays at your base until it's captured."),
jjHELPPARAGRAPH("When you roast a player carrying a flag, you steal it. Or if you already had a flag, the victim's flag stays there for 30 seconds before automatically returning to the base of the team with more points. If another player finds the discarded flag before it returns, they capture it."),
jjHELPPARAGRAPH("If all flags are at the same base, and that team isn't winning, every five seconds that team scores a free point."),
jjHELPPARAGRAPH("Strategy tip: don't capture a flag when injured, because the enemy team can wait for you to capture it, roast you, steal it back, and quickly return it to their base for a point.")
}, //end of array
"mutator gamemode multiflag"
));
if (jjIsServer || jjLocalPlayers[0].isAdmin)
pages.insertLast(jjHELPPAGE(
{jjHELPPARAGRAPH("Use the \\4! flagsperteam\\6 command to add even more flags to the game, for wilder gameplay. It defaults to 1.")},
"mutator gamemode multiflag commands",
"!flagsperteam"
));
}
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.