#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);
const auto SmallAsterisk = jjAnimations[jjAnimSets[ANIM::FONT].firstAnim + 1].firstFrame + 10;
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 (flag.playerID >= 0) {
const jjPLAYER@ holder = jjPlayers[flag.playerID];
jjDrawSpriteFromCurFrame(holder.xPos + jjGetStringWidth("§0" + holder.name, STRING::SMALL, STRING::NORMAL)/2, holder.yPos - 38, SmallAsterisk, 1, SPRITE::PALSHIFT, 256-24, -99);
}
}
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)];
}
}
}
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;
}
bool interceptChat = true;
bool onLocalChat(string &in stringReceived, CHAT::Type chatType) {
if (interceptChat) {
if (chatType == CHAT::WHISPER || chatType == CHAT::ME)
return false;
if (DoesPlayerHaveFlag(jjLocalPlayers[0]) is null)
return false;
if (stringReceived.length >= 1 && stringReceived[0] == '!'[0])
return false;
}
if (interceptChat) {
interceptChat = false;
jjChat("§K|||| §/*:|| " + stringReceived, chatType == CHAT::TEAMCHAT);
interceptChat = true;
return true;
} else {
return false;
}
}
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());
}