Name | Author | Game Mode | Rating | |||||
---|---|---|---|---|---|---|---|---|
Time Trial | PurpleJazz | Mutator | 10 |
#pragma name "Time Trial"
// Scripted by PurpleJazz with help from Sir Ementaler
/*******************************************************************/
//*MUTATOR DECLARATIONS*//
class checkpoint {
int xTile, yTile;
float xPos, yPos;
}
array<checkpoint> Checkpoints;
array<bool> StartedRace(32, false);
array<bool> TouchedCheckpoint(32, false);
array<uint64> Time(32, 0);
array<uint64> BestTime(32, 252000);
string formatTime(uint64 time) {
return (time / (70 * 60)) + ':' + formatInt(time / 70 % 60, '0', 2) + ':' + formatInt(time * 100 / 7 % 1000, '0', 3);
}
uint8 goalColor = 240;
uint8 cpColor = 248;
int goalTextOffset = 24;
int cpTextOffset = 52;
string goalText = "START";
string cpText = "CHECKPOINT";
const float PI = 3.1415927f;
int CTFArrowTimer = 0;
bool playSample = false;
bool hasBases = false;
/*******************************************************************/
//*RECORD SAVING DECLARATIONS*/- Based on SE's Test Manager code/
string lowercase(string &in text) {
for (int i = text.length() - 1; i >= 0; i--) {
if (text[i] > 64 && text[i] < 91)
text[i] ^= 32;
}
return text;
}
class record {
string name;
int time;
bool opEquals(const record &in other) const {
return lowercase(this.name) == lowercase(other.name);
}
int opCmp(const record &in other) const {
return this.time - other.time;
}
}
array<record> records;
string filename;
bool load(const string &in name) {
jjSTREAM file(name);
uint16 count;
if (!file.pop(count))
return false;
records.resize(count);
for (uint i = 0; i < count; i++) {
record@ rec = records[i];
if (!file.pop(rec.name) || !file.pop(rec.time)) {
records.resize(0);
return false;
}
}
return true;
}
void save(const string &in name) {
jjSTREAM file;
file.push(uint16(records.length()));
for (uint i = 0; i < records.length(); i++) {
const record@ rec = records[i];
file.push(rec.name);
file.push(rec.time);
}
file.save(name);
}
/*******************************************************************/
//*MAIN SCRIPT FUNCTIONS*//
void onLevelLoad() {
jjObjectPresets[OBJECT::CTFBASE].behavior = BEHAVIOR::INACTIVE;
jjObjectPresets[OBJECT::RFBULLET].behavior = jjObjectPresets[OBJECT::RFBULLETPU].behavior = rf;
for (int i = 1; i <= 9; i++) jjWeapons[i].infinite = true;
jjANIMATION@ anim = jjAnimations[jjAnimSets[ANIM::FLAG] + 3];
for (uint i = 0; i < anim.frameCount; i++) {
jjANIMFRAME@ frame = jjAnimFrames[anim + i];
jjPIXELMAP flag(frame);
for (uint x = 0; x < flag.width; x++) {
for (uint y = 0; y < flag.height; y++) {
if (flag[x, y] == 22)
flag[x, y] = 86;
else if (flag[x, y] == 23)
flag[x,y] = 87;
}
}
flag.save(frame);
}
for (int x = 0; x < jjLayerWidth[4]; x++) {
for (int y = 0; y < jjLayerHeight[4]; y++) {
if (jjEventGet(x, y) == OBJECT::CTFBASE) {
hasBases = true;
checkpoint newCheckpoint;
newCheckpoint.xTile = x;
newCheckpoint.yTile = y;
newCheckpoint.xPos = x * 32 + 16;
newCheckpoint.yPos = y * 32 + 16;
Checkpoints.insertLast(newCheckpoint);
}
if (jjEventGet(x,y) == OBJECT::RFAMMO3 || jjEventGet(x,y) == OBJECT::RFAMMO15) {
jjWeapons[WEAPON::RF].allowed = true;
}
if (jjEventGet(x,y) == OBJECT::RFPOWERUP) {
jjWeapons[WEAPON::RF].allowedPowerup = true;
}
}
}
if (jjIsServer) { //thanks to SE for this code
uint32 sum1 = 0, sum2 = 0;
for (int i = 0; i < jjLayerHeight[4]; i++) {
for (int j = 0; j < jjLayerWidth[4]; j++) {
sum1 += jjTileGet(4, j, i);
sum2 += sum1;
}
sum1 %= 0xFFFF;
sum2 %= 0xFFFF;
}
load(filename = "TimeTrial-" + formatInt(sum2 << 16 | sum1, '0H', 8) + ".asdat");
record rec;
rec.name = jjLocalPlayers[0].name;
int id = records.find(rec);
if (id >= 0) BestTime[jjLocalPlayers[0].playerID] = records[id].time;
}
else jjSendPacket(jjSTREAM());
}
[RFBULLET] void rf(jjOBJ@ obj) { //this is so your RF explosions won't affect other players
if (obj.creatorType == CREATOR::PLAYER && !jjPlayers[obj.creatorID].isLocal) {
obj.objType = HANDLING::EXPLOSION;
if (obj.state == STATE::EXPLODE) jjDrawSprite(obj.xPos, obj.yPos, ANIM::AMMO, 3, obj.curFrame, obj.direction, SPRITE::TRANSLUCENT);
}
obj.behave(BEHAVIOR::RFBULLET, obj.state == STATE::EXPLODE && !jjPlayers[obj.creatorID].isLocal? false:true);
}
void onLevelBegin() {
if (jjIsServer) {
if (jjGameMode != GAME::CTF || jjGameCustom != GAME::TB) jjChat("/tb");
if (!jjEnabledTeams[TEAM::BLUE]) jjChat("/teams blue on");
array<TEAM::Color> TeamsToDisable = {TEAM::RED, TEAM::YELLOW, TEAM::GREEN};
array<string> TeamColors = {"red", "yellow", "green"};
for (int i = 0; i < 3; i++) {
if (jjEnabledTeams[TeamsToDisable[i]]) jjChat("/teams " + TeamColors[i] + " off");
}
}
if (jjIsServer || jjIsAdmin) {
if (jjGameState == GAME::STOPPED) jjAlert("|The game must be started in order to play this mode!");
}
jjAlert("||Type !help for a list of commands.");
}
bool onDrawHealth(jjPLAYER@ play, jjCANVAS@ canvas) {
canvas.drawString(
jjSubscreenWidth-796,
jjSubscreenHeight-520,
"||||" + formatTime(Time[play.playerID]),
STRING::MEDIUM,
STRING::NORMAL
);
if (BestTime[play.playerID] < 252000)
canvas.drawString(
jjSubscreenWidth-796,
jjSubscreenHeight-496,
"|PB: " + formatTime(BestTime[play.playerID]),
STRING::SMALL,
STRING::NORMAL
);
return false;
}
void onMain() {
for (uint i = 0; i < Checkpoints.length(); i++) {
checkpoint@ cp = Checkpoints[i];
jjDrawSprite(cp.xPos + (i == 1? 16:-16), cp.yPos + 16, ANIM::FLAG, 3, jjGameTicks / 6 % 8, i == 1? -1:1, SPRITE::PALSHIFT, i == 1? cpColor : goalColor, 4, 4);
}
if (hasBases) {
jjDrawString(Checkpoints[0].xPos - goalTextOffset, Checkpoints[0].yPos - 42, goalText, STRING::SMALL, STRING::NORMAL);
jjDrawString(Checkpoints[1].xPos - cpTextOffset, Checkpoints[1].yPos - 42, cpText, STRING::SMALL, STRING::NORMAL);
}
if (CTFArrowTimer > jjGameTicks + 280) CTFArrowTimer = jjGameTicks;
if (CTFArrowTimer < jjGameTicks && hasBases) {
if (CTFArrowTimer + 64 >= jjGameTicks) {
int angle_A = int(atan2(Checkpoints[0].yPos - jjLocalPlayers[0].yPos, Checkpoints[0].xPos - jjLocalPlayers[0].xPos) * (512 / PI));
int angle_B = int(atan2(Checkpoints[1].yPos - jjLocalPlayers[0].yPos, Checkpoints[1].xPos - jjLocalPlayers[0].xPos) * (512 / PI));
const float scale = 64.f / (112.f - jjSin((jjGameTicks - CTFArrowTimer) << 3) * 64.f);
if (!StartedRace[jjLocalPlayers[0].playerID] || TouchedCheckpoint[jjLocalPlayers[0].playerID]) jjDrawRotatedSprite(jjLocalPlayers[0].xPos + 32 * jjCos(angle_A), jjLocalPlayers[0].yPos + 32 * jjSin(angle_A), ANIM::FLAG, 0, 0, 970 - angle_A, scale, scale, SPRITE::PALSHIFT, goalColor, 1);
else if (StartedRace[jjLocalPlayers[0].playerID] || !TouchedCheckpoint[jjLocalPlayers[0].playerID]) jjDrawRotatedSprite(jjLocalPlayers[0].xPos + 32 * jjCos(angle_B), jjLocalPlayers[0].yPos + 32 * jjSin(angle_B), ANIM::FLAG, 0, 0, 970 - angle_B, scale, scale, SPRITE::PALSHIFT, cpColor, 1);
} else {
CTFArrowTimer = jjGameTicks + 210;
}
}
if (jjIsServer) {
for (int i = 0; i < 32; i++) {
jjPLAYER@ play = jjPlayers[i];
if (play.isActive) {
for (uint j = 0; j < Checkpoints.length(); j++) {
checkpoint@ cp = Checkpoints[j];
if (int(play.xPos) / 32 == cp.xTile && int(play.yPos) / 32 == cp.yTile && jjGameState == GAME::STARTED) {
if (j == 0) {
if (!TouchedCheckpoint[play.playerID]) {
Time[play.playerID] = 0;
StartedRace[play.playerID] = true;
stream(i);
}
else {
if (Time[play.playerID] < BestTime[play.playerID] && TouchedCheckpoint[play.playerID]) {
BestTime[play.playerID] = Time[play.playerID];
record rec;
rec.name = play.name;
rec.time = BestTime[play.playerID];
int id = records.find(rec);
if (id < 0) records.insertLast(rec);
else records[id] = rec;
records.sortAsc();
save(filename);
jjAlert("|||||||" + play.name + " ||||set a new personal best of||||| " + formatTime(BestTime[play.playerID]), true);
if (play.isLocal) {
switch (play.charCurr) {
case CHAR::JAZZ: jjSamplePriority(SOUND::JAZZSOUNDS_JUMMY); break;
case CHAR::SPAZ: jjSamplePriority(SOUND::SPAZSOUNDS_HAPPY); break;
case CHAR::LORI: jjSamplePriority(SOUND::LORISOUNDS_WEHOO); break;
}
}
}
string newrecord = Time[play.playerID] <= BestTime[play.playerID]? "New record! " : "";
if (play.isLocal) {
jjSamplePriority(SOUND::COMMON_BELL_FIRE);
jjAlert("|||" + newrecord + "You finished in |||||" + formatTime(Time[play.playerID]), false, STRING::MEDIUM);
}
StartedRace[play.playerID] = false;
TouchedCheckpoint[play.playerID] = false;
stream(i);
}
}
if (j == 1 && StartedRace[play.playerID] && !TouchedCheckpoint[play.playerID]) {
TouchedCheckpoint[play.playerID] = true;
stream(i);
}
}
if (StartedRace[play.playerID] && j != 0 && jjGameState == GAME::STARTED) Time[play.playerID]++;
else if (jjGameState == GAME::STOPPED) {
Time[play.playerID] = 0;
StartedRace[play.playerID] = false;
TouchedCheckpoint[play.playerID] = false;
}
if (play.invincibility < 0) {
StartedRace[play.playerID] = false;
TouchedCheckpoint[play.playerID] = false;
Time[play.playerID] = 0;
stream(i);
}
}
}
}
if (TouchedCheckpoint[jjLocalPlayers[0].playerID]) {
goalColor = 8;
cpColor = 0;
goalTextOffset = 16;
cpTextOffset = 32;
goalText = "GOAL";
cpText = "RETURN";
}
else {
goalColor = 240;
cpColor = 248;
goalText = "START";
cpText = "CHECKPOINT";
cpTextOffset = 52;
goalTextOffset = 24;
}
}
else {
if (StartedRace[jjLocalPlayers[0].playerID] && jjGameState == GAME::STARTED) Time[jjLocalPlayers[0].playerID]++;
else if (jjGameState == GAME::STOPPED) {
Time[jjLocalPlayers[0].playerID] = 0;
StartedRace[jjLocalPlayers[0].playerID] = false;
TouchedCheckpoint[jjLocalPlayers[0].playerID] = false;
}
if (jjLocalPlayers[0].invincibility < 0) {
Time[jjLocalPlayers[0].playerID] = 0;
StartedRace[jjLocalPlayers[0].playerID] = false;
TouchedCheckpoint[jjLocalPlayers[0].playerID] = false;
}
if (playSample) {
jjSamplePriority(SOUND::COMMON_BELL_FIRE);
if (Time[jjLocalPlayers[0].playerID] < BestTime[jjLocalPlayers[0].playerID]) {
switch (jjLocalPlayers[0].charCurr) {
case CHAR::JAZZ: jjSamplePriority(SOUND::JAZZSOUNDS_JUMMY); break;
case CHAR::SPAZ: jjSamplePriority(SOUND::SPAZSOUNDS_HAPPY); break;
case CHAR::LORI: jjSamplePriority(SOUND::LORISOUNDS_WEHOO); break;
}
}
playSample = false;
}
if (TouchedCheckpoint[jjLocalPlayers[0].playerID]) {
goalColor = 8;
cpColor = 0;
goalTextOffset = 16;
cpTextOffset = 32;
goalText = "GOAL";
cpText = "RETURN";
}
else {
goalColor = 240;
cpColor = 248;
goalText = "START";
cpText = "CHECKPOINT";
cpTextOffset = 52;
goalTextOffset = 24;
}
}
}
/*******************************************************************/
//*CHAT COMMANDS*//
void onChat(int clientID, string &in text, CHAT::Type) {
array<string> results;
if (jjRegexMatch(text, """!(.*\S)\s*""", results)) {
text = results[1];
if (jjIsServer) {
if (jjRegexMatch(text, """pb(\s+(.+))?""", results, true) || jjRegexMatch(text, """rank(\s+(.+))?""", results, true)) {
string name = results[2];
record rec;
rec.name = name;
int id = records.find(rec);
if (name.isEmpty()) {
jjAlert("|Please provide a valid player name.", true);
}
else {
if (id >= 0) jjAlert("|||||||" + records[id].name + " ||||has a personal best of |||||" + formatTime(records[id].time) + " ||- ranked " + (id + 1) + " of " + records.length(), true);
else jjAlert("|That player does not have a best time for this level.", true);
}
}
else if (jjRegexMatch(text, """top""", true)) {
if (records.length() >= 5) {
for (uint i = 0; i < 5; i++) {
const record@ rec = records[i];
jjAlert("||" + (i + 1) + ". |||||" + rec.name + "| - " + formatTime(rec.time), true);
}
}
else if (records.length() < 5 && !records.isEmpty()) {
for (uint i = 0; i < records.length(); i++) {
const record@ rec = records[i];
jjAlert("||" + (i + 1) + ". |||||" + rec.name + "| - " + formatTime(rec.time), true);
}
}
else if (records.isEmpty()) {
jjAlert("|No one has set any records for this level yet!", true);
}
}
else if (jjRegexMatch(text, """whois\s*([0-9]\d*)""", results, true)) {
uint id = parseInt(results[1]) - 1;
if (id < records.length()) {
const record@ rec = records[id];
jjAlert("|||||||" + rec.name + " ||||is ranked||||||| " + (id + 1) + " of " + records.length() + " |with a best time of |||||" + formatTime(rec.time), true);
}
else jjAlert("|No player has been found with that ranking.", true);
}
else if (jjRegexMatch(text, """avg""", true) || jjRegexMatch(text, """average""", true)) {
uint sum = 0;
for (uint i = 0; i < records.length(); i++) {
sum += records[i].time;
}
if (!records.isEmpty()) jjAlert("|||The average time recorded in this level is |||||" + formatTime(sum / records.length()), true);
else jjAlert("|No average can be calculated because no times have been recorded!", true);
}
else if (jjRegexMatch(text, """delete(\s+(.+))?""", results, true) && (clientID == jjPlayers[0].clientID || jjPlayers[clientID].isAdmin)) {
string name = results[2];
record rec;
rec.name = name;
int id = records.find(rec);
if (name.isEmpty()) {
jjAlert("|Please provide a valid player name.", true);
}
else if (id >= 0) {
jjAlert("|||" + records[id].name + "'s record of |||||" + formatTime(records[id].time) + " |||has been ||||||DELETED", true);
records.removeAt(id);
records.sortAsc();
save(filename);
for (int i = 0; i < 32; i++) {
if (jjPlayers[i].name == name) {
BestTime[i] = 252000;
stream(i);
}
}
}
else jjAlert("|That player does not have a best time.", true);
}
}
}
}
bool onLocalChat(string &in text, CHAT::Type type) {
if (type == CHAT::NORMAL && text == "!help") {
jjAlert("|||!getrf ||- gives you RF ammo if obtainable; PU if a monitor is present");
jjAlert("|||!top ||- shows the 5 fastest times and who recorded them");
jjAlert("|||!rank <player name> ||- shows the best time and the rank of specific player");
jjAlert("|||!whois <rank> ||- shows the best time of a player with that rank");
jjAlert("|||!avg ||- calculates the average time taken by all player runs");
if (jjIsServer || jjIsAdmin) {
jjAlert("|||!delete <player name> ||- erases the record of a specified player");
}
return true;
}
if (type == CHAT::NORMAL && text == "!getrf" || text == "!getRF") {
if (jjWeapons[WEAPON::RF].allowed) {
jjLocalPlayers[0].ammo[WEAPON::RF] = 1;
jjLocalPlayers[0].currWeapon = WEAPON::RF;
if (!jjWeapons[WEAPON::RF].allowedPowerup) jjAlert(">> |||Received RF");
}
if (jjWeapons[WEAPON::RF].allowedPowerup) {
jjLocalPlayers[0].powerup[WEAPON::RF] = true;
jjLocalPlayers[0].ammo[WEAPON::RF] = 1;
jjLocalPlayers[0].currWeapon = WEAPON::RF;
jjAlert(">> |||Received RF");
}
else if (!jjWeapons[WEAPON::RF].allowed) jjAlert("|>> No RF ammo could be found in this level");
return true;
}
return false;
}
/*******************************************************************/
//*SERVER-CLIENT NETWORKING*//
void stream(int id) {
if (jjPlayers[id].clientID != 0) {
jjSTREAM packet;
packet.push(!StartedRace[id] && Time[id] > 0? true:false);
packet.push(Time[id]);
packet.push(BestTime[id]);
packet.push(StartedRace[id]);
packet.push(TouchedCheckpoint[id]);
jjSendPacket(packet, jjPlayers[id].clientID);
}
}
void onReceive(jjSTREAM &in packet, int clientID) {
if (!jjIsServer) {
bool x;
uint64 time;
uint64 best_time;
bool started_race;
bool touched_checkpoint;
packet.pop(x);
packet.pop(time);
packet.pop(best_time);
packet.pop(started_race);
packet.pop(touched_checkpoint);
Time[jjLocalPlayers[0].playerID] = time;
BestTime[jjLocalPlayers[0].playerID] = best_time;
StartedRace[jjLocalPlayers[0].playerID] = started_race;
TouchedCheckpoint[jjLocalPlayers[0].playerID] = touched_checkpoint;
string newrecord = Time[jjLocalPlayers[0].playerID] <= BestTime[jjLocalPlayers[0].playerID]? "New record! " : "";
if (x) {
jjAlert("|||" + newrecord + "You finished in |||||" + formatTime(Time[jjLocalPlayers[0].playerID]), false, STRING::MEDIUM);
playSample = true;
}
}
else {
record rec;
rec.name = jjPlayers[clientID].name;
int id = records.find(rec);
if (id >= 0) BestTime[clientID] = records[id].time;
stream(clientID);
}
}
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.