Skip to content

Instantly share code, notes, and snippets.

@roxwize
Created March 14, 2024 00:30
Show Gist options
  • Select an option

  • Save roxwize/727c2881e1ae6141c61c8dfe04cf8327 to your computer and use it in GitHub Desktop.

Select an option

Save roxwize/727c2881e1ae6141c61c8dfe04cf8327 to your computer and use it in GitHub Desktop.
TEA FORTRESS 8
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>TEA FORTRESS 8</title>
<link href="style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<h1>TEA FORTRESS 8</h1>
<div id="game">
<pre id="playfield"></pre>
<div id="p-stats">
Turn <strong id="p-turn">0</strong><br><br>
<strong id="p-class">None</strong><br>
<strong id="p-health">0</strong>/<strong id="p-health-max">0</strong>HP<br><br>
<strong id="p-wep-name">None</strong><br>
<strong id="p-ammo-clip">0</strong>/<strong id="p-ammo-reserve">0</strong><br><br>
<strong id="p-status"></strong>
</div>
<div id="leaderboard"></div>
</div>
<h3>Chat</h3>
<pre id="chat"></pre>
<input type="text" id="chatinput" placeholder="ENTER to send" autocomplete="off">
<h3>The Controls</h3>
<p>you can also use WASD/arrow keys to move</p>
<p>click on a player to shoot in their general direction</p>
<button onclick="player.move(-1,0);doTurn()">W</button><button onclick="player.move(0,-1);doTurn()">N</button><button onclick="player.move(0,1);doTurn()">S</button><button onclick="player.move(1,0);doTurn()">E</button><button onclick="if(player.reload())doTurn()">RELOAD</button><button onclick="doTurn()">PASS</button><br>
<button id="p-b-primary">PRIMARY</button><button id="p-b-secondary">SECONDARY</button><button id="p-b-tertiary">TERTIARY</button><button id="p-b-melee">MELEE</button><br>
<button onclick="player.sayVoiceline(VOICELINE_TYPE.TAUNT)">TAUNT</button>
<button onclick="player.sayVoiceline(VOICELINE_TYPE.TAUNT2)">TAUNT2</button><br>
<button onclick="player.kill();doTurn()">DIE</button><br>
<button onclick="bot()">BOT</button><br>
<button id="d-kill-update">UPDATE</button> <select id="d-kill-select"></select> <button id="d-kill-btn">KILL</button> <button id="d-kill-from">DAMAGE FROM</button>
<h3>The Information</h3>
<p>This is the work of <a href="//roxwize.xyz">roxwize</a></p>
<p>Team Fortress belongs to Valve Software</p>
<script src="index.js"></script>
</body>
</html>
/***********************/
/****TEA FORTRESS 2*****/
/***(ↄ) roxwize 2024****/
/*****tf2 by valve******/
/***********************/
/******v0.1.0 !!********/
/***********************/
const WEAPON_TYPE = {
PRIMARY: 0,
SECONDARY: 1,
TERTIARY: 2,
MELEE: 3,
NONE: 4,
toString: (type) => {
switch (type) {
case WEAPON_TYPE.PRIMARY:
return "primary";
case WEAPON_TYPE.SECONDARY:
return "secondary";
case WEAPON_TYPE.TERTIARY:
return "tertiary";
case WEAPON_TYPE.MELEE:
return "melee";
case WEAPON_TYPE.NONE:
return "";
}
}
}
const VOICELINE_TYPE = {
YES: 0, // Da
NO: 1, // Nyet
SPY: 2, // Icy spy
SPY_IDENTIFY: 3, // Heavy is pie
HELP: 4, // Halp meh
PAIN: 5, // Oof
TAUNT: 6, // I was told we would be fighting men
TAUNT2: 7, // Im coming for yuoo,
CRIT_DEATH: 8, // AAAAAAAAAGHHYGAYADHAAAHH
CRIT_PAIN: 9 // Ouuuggghhhhh
}
const VOICELINE_DEFAULT = [
"Yes.",
"No.",
"I sense a spy.",
"That {} is a spy.",
"Help me!",
"Yowch!",
"I am taunting you.",
"I am taunting you in a different fashion.",
"AAAOUUGHHH!!!!!!!!",
"YOWWW!!!!!"
]
/////////////////////////////////////////
// WEAPON CLASS //
/////////////////////////////////////////
class WeaponStats {
constructor() {
this.damage = 12;
this.damageScaling = 0;
this.range = 6;
this.maxClip = 0;
}
setDamage(dmg) {
this.damage = dmg;
return this;
}
setDamageScaling(scale) {
this.damageScaling = scale;
return this;
}
setRange(range) {
this.range = range;
return this;
}
setMaxClip(clip) {
this.maxClip = clip;
return this;
}
}
class Weapon {
/**
* @param {number} type
* @param {string} name
* @param {WeaponStats} stats
*/
constructor(type, name, stats) {
this.type = type;
this.name = name;
this.stats = stats;
}
}
/////////////////////////////////////////
// CLASS CLASS(??) //
/////////////////////////////////////////
class ClassStats {
constructor() {
this.speed = 1;
this.maxHp = 200;
}
setSpeed(speed) {
this.speed = speed;
return this;
}
setMaxHp(hp) {
this.maxHp = hp;
return this;
}
}
class Class {
/**
* @param {string} name
* @param {Weapon[]} loadout
* @param {ClassStats} stats
*/
constructor(name, loadout, stats) {
this.name = name;
this.defaultLoadout = loadout;
this.stats = stats;
this.voicelines = VOICELINE_DEFAULT;
}
setVoicelines(voicelines) {
this.voicelines = voicelines;
return this;
}
}
class DamageInfo {
/**
* @param {number} damage
* @param {Mercenary} damager
* @param {Mercenary} damagee
* @param {boolean} crit
*/
constructor(damage, damager, damagee, crit = false) {
this.damage = damage;
this.damager = damager;
this.damagee = damagee;
this.critical = crit;
if (this.damager == this.damagee) this.selfInflicted = true;
if (this.critical) this.damage *= 3;
this.weapon = null;
}
/**
* param {Weapon} weapon
*/
setWeapon(weapon) {
this.weapon = weapon;
return this;
}
toKillfeedString() {
return "<strong>" + (this.selfInflicted ? `${this.damagee.getName()} bid farewell, cruel world!`
: `${this.critical ? "[CRIT!] " : ""}${this.damager.getName()} killed ${this.damagee.getName()}${this.weapon && this.weapon != Weapons._INTERNAL_world ? ` with ${this.weapon.name}` : ""}`) + "</strong>";
}
}
/////////////////////////////////////////
// MERCENARY CLASS //
/////////////////////////////////////////
class Mercenary {
/**
* @param {string} name Username
* @param {Class} classType
* @param {{x:number,y:number}} pos
*/
constructor(name, classType, pos = { x: 0, y: 0 }, accuracy = 0.8) {
this.name = name;
this.classType = classType;
this.pos = pos;
this.kills = 0;
this.deaths = 0;
this.accuracy = accuracy;
this.isBot = false;
this.isPlayer = false;
this.health = classType.stats.maxHp;
this.lastDamage = null;
this.dead = false;
this.respawnTime = 0;
this.loadout = classType.defaultLoadout;
this.clip = [this.loadout[0]?.stats.maxClip || 0, this.loadout[1]?.stats.maxClip || 0, this.loadout[2]?.stats.maxClip || 0];
this.reserve = [this.clip[0] * 4, this.clip[1] * 4, this.clip[2] * 4];
this.weaponId = WEAPON_TYPE.PRIMARY;
}
getName() {
return `${this.dead ? "[DEAD] " : ""}${this.name}`;
}
move(dx, dy) {
if (this.dead) return;
if (dx !== 0) {
if (playfield[this.pos.y][this.pos.x + dx] === undefined) return false;
if (playerColMap[this.pos.y][this.pos.x + dx]) return false; // check entity collision map
if (playfield[this.pos.y][this.pos.x + dx] === 1) return false; // check world
// assume it's 1, otherwise we can pass through walls which might need to be fixed at some point
else this.pos.x += dx;
}
if (dy !== 0) {
if (playfield[this.pos.y + dy] == undefined) return false;
if (playerColMap[this.pos.y + dy][this.pos.x]) return false; // check entity collision map
if (playfield[this.pos.y + dy][this.pos.x] === 1) return false; // check world
else this.pos.y += dy;
}
updateColMap();
return true;
}
reload() {
const clipSize = this.loadout[this.weaponId].stats.maxClip;
if (this.clip[this.weaponId] === clipSize) return false;
const reserve = this.reserve[this.weaponId];
const toReload = clipSize - this.clip[this.weaponId];
if (reserve > toReload) {
// dont empty
this.reserve[this.weaponId] -= toReload;
this.clip[this.weaponId] += toReload;
statusText = "Reloading...";
} else if (reserve > 0) {
// empty all of reserve into clip
this.clip[this.weaponId] += this.reserve[this.weaponId];
this.reserve[this.weaponId] = 0;
statusText = "Reloading...";
} else {
statusText = "Empty magazine.";
printStats();
return false;
}
return true;
}
fire(merc) {
if (this.dead || merc == this) return false; // Can't fire at yo self
const wep = this.loadout[this.weaponId];
if (this.clip[this.weaponId]) {
let accuracy = this.accuracy;
const dist = this.distanceTo(merc.pos.x, merc.pos.y);
// lower accuracy if range exceeded
if (dist > wep.stats.range) {
accuracy = 0.3;
}
if (Math.random() < accuracy) {
// didn't miss
let damage = wep.stats.damage;
const scale = wep.stats.damageScaling * (dist - 1);
if (wep.stats.damageScaling && scale > 0) {
// damage scaling per range (dumb bodge for shotgun mechanics)
damage = Math.floor(wep.stats.damage / scale);
}
const crit = Math.random() > 0.7 // Random crits :3
if (crit) statusText = "CRITICAL HIT!!";
else statusText = `Hit ${merc.getName()}!`;
statusText += `<br>${crit ? damage * 3 : damage} DMG`;
merc.hurt(new DamageInfo(damage, this, merc, crit).setWeapon(wep));
} else {
statusText = "MISS!"
}
this.clip[this.weaponId]--;
} else {
this.reload();
}
return true;
}
say(message) {
say(`${this.getName()}: ${message}`, false);
}
sayVoiceline(voiceline) {
this.say("[VOICE] " + this.classType.voicelines[voiceline]);
}
/**
* @param {DamageInfo} dmginfo
*/
hurt(dmginfo) {
if (this.dead) return;
this.health -= dmginfo.damage;
this.lastDamage = dmginfo;
if (/*Math.random() > 0.5 && */!dmginfo.critical) {
this.sayVoiceline(VOICELINE_TYPE.PAIN);
} else if (dmginfo.critical && this.health > 0) {
this.sayVoiceline(VOICELINE_TYPE.CRIT_PAIN);
}
// Player death logic
if (this.health <= 0) {
say(dmginfo.toKillfeedString()) // Log to chat
this.health = 0; // clamp
this.dead = true; // X_X
this.deaths++;
if (dmginfo.damager && !dmginfo.selfInflicted) dmginfo.damager.kills++;
this.respawnTime = 15; // TODO: this is a placeholder value
// Crit death scream
if (dmginfo.critical) this.sayVoiceline(VOICELINE_TYPE.CRIT_DEATH);
}
printStats();
printLeaderboard();
}
kill() {
this.hurt(
new DamageInfo(this.classType.stats.maxHp * 5, this, this, true).setWeapon(Weapons._INTERNAL_world)
);
}
respawn() {
this.dead = false;
this.health = this.classType.stats.maxHp;
this.respawnTime = 0;
this.lastDamage = null;
}
distanceTo(x, y) {
return Math.sqrt(Math.pow(x - this.pos.x, 2) + Math.pow(y - this.pos.y, 2));
}
}
class Bot extends Mercenary {
constructor(name, classType, pos = { x: 0, y: 0 }) {
super(name, classType, pos);
this.isBot = true;
}
getName() {
return `${this.dead ? "[DEAD] " : ""}[BOT]${this.name}`;
}
think() {
if (this.dead) return;
this.move(
Math.round(Math.random() * 2) - 1,
Math.round(Math.random() * 2) - 1
)
}
}
const Weapons = {
Pistol: new Weapon(WEAPON_TYPE.SECONDARY, "Pistol", new WeaponStats().setDamage(12).setRange(15).setMaxClip(12)),
Bat: new Weapon(WEAPON_TYPE.MELEE, "Bat", new WeaponStats().setDamage(33).setRange(1).setMaxClip(0)),
Scattergun: new Weapon(WEAPON_TYPE.PRIMARY, "Scattergun", new WeaponStats().setDamage(75).setRange(6).setMaxClip(6).setDamageScaling(1.3)),
_INTERNAL_world: new Weapon(WEAPON_TYPE.NONE, "world", new WeaponStats().setDamage(1E10))
};
const Classes = {
Scout: new Class(
"Scout",
[Weapons.Scattergun, Weapons.Pistol, null, Weapons.Bat],
new ClassStats()
.setSpeed(2)
.setMaxHp(125))
.setVoicelines([
"Yeah!", "Uhh, no.", "Hey, there's a spy over here!", "That frickin' {}'s a spy.", "I'm dyin' here!", "Augh!", "I don't usually kill morons this fast.", "I'm a force o' nature.", "AAAAAAAAAAAAAGGHHHH!!", "AaaaAaAAaACk!"
]),
// Soldier: new Class(
// "Soldier",
// [null, null, null, null],
// new ClassStat()
// .setSpeed(1)
// .setMaxHp(200))
// .setVoicelines([
// "Affirmative!", "Negatory!", "Boys, we have a traitor!", "That {} is a spy!", "Heeeelp!", "Agh!", "That was an amazing killing spree... by the other team!", "Pain is weakness leaving the body.", "AAUUUUAUUUUUUUUUUGGHH!!!!"
// ])
}
const playfield = [
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
];
let playerColMap;
const player = new Mercenary("Player", Classes.Scout, {
x: 14, y: 14
});
player.isPlayer = true;
const mercs = [player];
let turn = 0;
/////////////////////////////////////////
// UI ELEMENTS //
/////////////////////////////////////////
const chatEl = document.getElementById("chat");
const chatInputEl = document.getElementById("chatinput");
const playfieldEl = document.getElementById("playfield");
const statEls = {
turn: document.getElementById("p-turn"),
class: document.getElementById("p-class"),
hp: document.getElementById("p-health"),
hpMax: document.getElementById("p-health-max"),
weaponName: document.getElementById("p-wep-name"),
ammoClip1: document.getElementById("p-ammo-clip"),
ammoReserve1: document.getElementById("p-ammo-reserve"),
status: document.getElementById("p-status"),
weaponControls: [
document.getElementById("p-b-primary"),
document.getElementById("p-b-secondary"),
document.getElementById("p-b-tertiary"),
document.getElementById("p-b-melee")
]
}
const leaderboardEl = document.getElementById("leaderboard");
const _DEBUG_killSelectEl = document.getElementById("d-kill-select");
function updateColMap() {
playerColMap = [];
for (let y = 0; y < playfield.length; y++) {
playerColMap.push([]);
for (let x = 0; x < playfield[y].length; x++) {
playerColMap[y].push(false);
}
}
mercs.forEach((merc) => {
playerColMap[merc.pos.y][merc.pos.x] = true;
})
}
/////////////////////////////////////////
// UI RENDERING //
/////////////////////////////////////////
function printPlayfield() {
const out = [];
for (let y of playfield) {
for (let x of y) {
out.push(x ? (x === 1 ? "#" : x) : ".");
}
out.push("<br>");
}
mercs.forEach((merc, idx) => {
const pos = (merc.pos.y * (playfield.length + 1)) + merc.pos.x;
let sprite = "O";
if (merc.isPlayer) sprite = "Y"
if (merc.dead) sprite = "X"
out[pos] = `<span class="merc" data-name="${merc.getName()} [${merc.classType.name}]" onclick="if(player.fire(mercs[${idx}]))doTurn()">${sprite}</span>`;
});
playfieldEl.innerHTML = out.join("");
}
let statusText = "";
function printStats() {
statEls.class.innerHTML = player.classType.name;
statEls.hp.innerHTML = player.health;
statEls.hpMax.innerHTML = player.classType.stats.maxHp;
statEls.weaponName.innerHTML = player.loadout[player.weaponId].name;
statEls.ammoClip1.innerHTML = player.clip[player.weaponId];
statEls.ammoReserve1.innerHTML = player.reserve[player.weaponId];
if (player.dead) {
statEls.status.innerHTML = `Killed by ${player.lastDamage.selfInflicted ? "yourself!" : player.lastDamage.damager.getName()}<br>${player.respawnTime} turns 'til respawn`
} else {
statEls.status.innerHTML = statusText;
}
// Weapon switcher
for (let wep in player.loadout) {
const ctrl = statEls.weaponControls[wep];
const weapon = player.loadout[wep];
if (weapon) {
ctrl.innerHTML = weapon.name.toUpperCase();
ctrl.onclick = () => {
player.weaponId = wep;
statusText = `Switched to ${weapon.name}...`;
printStats();
}
} else {
ctrl.innerHTML = "NONE";
ctrl.disabled = true;
}
}
}
function printLeaderboard() {
let out = "";
mercs.forEach((merc) => {
out += `${merc.getName()} (${merc.kills}/${merc.deaths})<br>`
});
leaderboardEl.innerHTML = out;
}
function printChat() {
chatEl.innerHTML = chat.join("<br>");
}
function doTurn() {
turn++;
statEls.turn.innerHTML = turn;
mercs.forEach((merc) => {
if (merc.dead) {
merc.respawnTime--;
if (merc.respawnTime === 0) merc.respawn();
}
if (merc.isBot) {
merc.think();
}
});
printPlayfield();
printStats();
}
function randArray(array) {
return array[Math.floor(Math.random() * array.length)];
}
function randObject(object) {
const e = Object.entries(object);
return e[Math.floor(Math.random() * e.length)][1];
}
let chat = new Array(5).fill("");
function say(msg, system = false) {
chat.shift();
chat.push(`${system ? "[System] " : ""}${msg}`);
printChat();
}
const botNames = ["0xDEADBEEF", "Herr Doktor", "Archimedes", "GLaDOS", "GutsAndGlory!", "HI THERE", "AmNot", "AimBot", "A Professional With Standards", "Glorified Toaster with Legs", "Zepheniah Mann", "KillMe", "Numnutz", "CRITAWKETS", "C++", "CrySomeMore", "Me", "Force of Nature", "Crazed Gunman", "NotMe", "H@XX0RZ", "Hostage", "Poopy Joe", "Pow!", "RageQuit", "Ribs Grow Back", "Saxton Hale", "Screamin' Eagles", "SMELLY UNFORTUNATE", "GENTLE MANNE of LEISURE", "MoreGun"];
function bot(callout = false) {
let pos = {
x: Math.floor(Math.random() * playfield.length),
y: Math.floor(Math.random() * playfield[0].length)
};
while (playerColMap[pos.x][pos.y]) {
pos = {
x: Math.floor(Math.random() * playfield.length),
y: Math.floor(Math.random() * playfield[0].length)
};
}
const merc = (
new Bot(randArray(botNames), randObject(Classes), pos)
);
mercs.push(merc);
say(`${merc.getName()} joined the game`);
if (callout) {
merc.sayVoiceline(Math.floor(Math.random() * 9));
}
printPlayfield();
printLeaderboard();
}
/////////////////////////////////////////
// EVENT LISTENERS //
/////////////////////////////////////////
document.addEventListener("DOMContentLoaded", () => {
statusText = "Welcome to Tea Fortress 8";
updateColMap();
printPlayfield();
printStats();
printLeaderboard();
say("Wecome to Tea Fortress 8", true);
for (let _ = 0; _ < 5; _++) {
bot();
}
updateColMap();
});
document.addEventListener("keydown", (ev) => {
switch (ev.code) {
case "KeyW":
case "ArrowUp":
player.move(0, -1);
doTurn();
break;
case "KeyA":
case "ArrowLeft":
player.move(-1, 0);
doTurn();
break;
case "KeyS":
case "ArrowDown":
player.move(0, 1);
doTurn();
break;
case "KeyD":
case "ArrowRight":
player.move(1, 0);
doTurn();
break;
case "KeyR":
if (player.reload()) doTurn();
break;
}
});
chatInputEl.addEventListener("keypress", (ev) => {
if (ev.code !== "Enter") return;
player.say(chatInputEl.value);
chatInputEl.value = "";
});
/* DEBUG */
document.getElementById("d-kill-update").onclick = () => {
for (let child of _DEBUG_killSelectEl.children) {
_DEBUG_killSelectEl.removeChild(child);
}
mercs.forEach((el, idx) => {
const opt = document.createElement("option");
opt.innerHTML = `${idx} ${el.getName()}`;
opt.value = idx;
_DEBUG_killSelectEl.appendChild(opt);
});
};
document.getElementById("d-kill-btn").onclick = () => {
const merc = mercs[parseInt(_DEBUG_killSelectEl.value)];
merc.hurt(new DamageInfo(1000, player, merc, true).setWeapon(Weapons._INTERNAL_world));
doTurn();
};
document.getElementById("d-kill-from").onclick = () => {
player.hurt(new DamageInfo(25, mercs[parseInt(_DEBUG_killSelectEl.value)], player));
doTurn();
};
html {
height: 100%;
width: 100%;
}
body {
font-family: monospace;
font-feature-settings: "liga" 0;
font-variant-ligatures: none;
}
#game {
display: flex;
flex-direction: row;
gap: 1rem;
}
#playfield {
line-height: 8px;
cursor: default;
}
#playfield > span:hover {
cursor: pointer;
background-color: rgba(0, 0, 0, 0.25);
}
#p-stats {
background-color: rgba(0, 0, 0, 0.1);
border: 1px solid rgba(0, 0, 0, 0.2);
padding: 0.5em;
}
#leaderboard {
max-height: 45vh;
overflow-y: auto;
}
pre, p {
margin: 0;
}
button, input {
border: 1px solid black;
border-radius: 0;
margin: 3px;
background-color: black;
color: white;
}
button:not(:disabled):hover, input:hover {
background-color: white;
color: black;
cursor: pointer;
}
button:focus-visible, input:focus-visible {
outline: 0;
}
button:disabled {
opacity: 0.5;
}
.merc {
position: relative;
}
.merc::after {
position: absolute;
top: -50%;
left: 12px;
background-color: black;
color: white;
content: attr(data-name);
padding: 4px;
opacity: 0;
height: fit-content;
width: 0;
pointer-events: none;
z-index: 99;
}
.merc:hover::after {
opacity: 1;
width: fit-content;
cursor: help;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment