Recently a friend of mine from college said he’ll do some programming work for me and I had him redo the defusal game mode (http://aloha.pk/index.php?topic=9452.0)
So far I briefly tested it on piqueserver with openspades and it looks like it works somewhat.
ATM there is only one bombsite and the base gamemode is CTF. According to my friend, two bombsites could be possible (along with with progression bar) if base game mode was TC). The code can probably be improved upon by others who are more familiar with aos/piqueserver mechanics.
Code
"""
Bombdefusal: Game mode based on Counter Strike
Green Team is Terrorist
Blue Team is Counter-Terrorist
Green Team starts with enemy intel (BOMB) amd must take it to their base (BOMB SITE) and plant it there.
The have to hold the bomb site in order to prevent CT's from defusing the bomb.
The game is won by either killing all members of enemy team or successfully detonating the bomb.
Blue Team's main objective is to prevent Green Team from planting the bomb.
The game is won by either killing all members of enemy team or successfully defusing the bomb.
Spawn and Bomb Site locations must be configured via extensions in the map's
map_name.txt metadata:
>>> extensions = {
... 'ct_spawn': (235, 310, 58),
... 't_spawn': (275, 180, 49),
... 'bomb_site': (205, 307, 52),
... }
Optionally, the following variables can also be changed:
-> bomb_duration
-> bomb_radius
-> defuse_time
-> plant_time
"""
from __future__ import division
import random
import time
from twisted.internet.error import AlreadyCalled, AlreadyCancelled
from twisted.internet.reactor import callLater
from twisted.internet.task import LoopingCall
from pyspades.collision import distance_3d
from pyspades.common import Vertex3
from pyspades.constants import *
from pyspades.contained import ChatMessage, GrenadePacket
from pyspades.world import Grenade
chat_message, grenade_packet = ChatMessage(), GrenadePacket()
out_of_map = (0, 0, 63) # Location to hide unwanted elements of the game
ROUND_TIME = 120 # In seconds
BOMB_TIME = 40 # bomb duration
def bomb_calc(bomb_pos, player_pos, blast_radius): # calculation of bomb damage based on distance
distance = distance_3d(bomb_pos, player_pos)
if distance > blast_radius:
return 0
return 1.2 - distance / blast_radius
def all_dead(team): # check if entire team is dead
for player in team.get_players():
if not player.world_object.dead:
return False
return True
def send_chat(connection, value, global_message): # sending chat to other team members
chat_message.player_id = connection.player_id
chat_message.value = value
chat_message.chat_type = [CHAT_TEAM, CHAT_ALL][int(global_message)]
connection.protocol.send_contained(chat_message, team=None if global_message else connection.team)
def apply_script(protocol, connection, config):
class BombdefusalConnection(connection):
defusing = False
planting = False
def cancel_actions(self): # cancel all actions that a player can perform
if self.defusing:
self.protocol.stop_defusing(canceled=True, connection=self)
if self.planting:
self.protocol.stop_planting(canceled=True, connection=self)
def on_disconnect(self):
if self.protocol.match_running:
self.cancel_actions()
if self.world_object is not None:
self.world_object.dead = True # kill players on disconnect
self.protocol.check_game_status()
return connection.on_disconnect(self)
def on_team_join(self, team):
ret = connection.on_team_join(self, team)
if ret is False:
return ret
if self.protocol.match_running:
self.cancel_actions()
if self.world_object is not None and not self.world_object.dead: # kill joining player if match is already in progress
self.world_object.dead = True
self.protocol.check_game_status()
return ret
def get_respawn_time(self):
if self.protocol.match_running:
return -1
elif self.protocol.game_enabled:
return 0
return connection.get_respawn_time(self)
def respawn(self):
if self.protocol.match_running:
self.cancel_actions()
return False # no respawn which round is in progress
return connection.respawn(self)
def on_spawn(self, position):
ret = connection.on_spawn(self, position)
if self.protocol.match_running:
self.cancel_actions()
self.kill() # kill joining player if match is already in progress
return ret
def on_spawn_location(self, pos):
if self.protocol.game_enabled:
self.cancel_actions()
return self.team.spawn # set team spawn location
return connection.on_spawn_location(self, pos)
def on_kill(self, killer, type, grenade):
if self.protocol.match_running:
if self.world_object is not None:
self.world_object.dead = True
self.protocol.check_game_status() # check game status after each player kill
if self.protocol.match_running:
self.cancel_actions()
return connection.on_kill(self, killer, type, grenade)
def on_refill(self):
ret = connection.on_refill(self)
if self.protocol.match_running:
return False
return ret
def on_flag_take(self): # event that is triggered when Counter-Terrorist come near the bomb (green intel).
if not self.protocol.game_enabled or not self.protocol.match_running:
return connection.on_flag_take(self)
if self.team == self.protocol.green_team:
if self.protocol.destruction and self.protocol.destruction.active():
return False
self.send_chat("You picked up the C4 explosive, take it to the bombsite!")
return connection.on_flag_take(self)
elif (self.protocol.destruction and self.protocol.destruction.active() and
self.team == self.protocol.blue_team):
if self.protocol.defuse_timer:
return False
if self.tool != BLOCK_TOOL:
self.send_chat("You must have your BLOCK TOOL out in order to defuse the bomb!")
return False
ret = connection.on_flag_take(self)
self.defusing = True
send_chat(self, "I'm defusing the bomb.", global_message=False)
self.protocol.defuse_pos = self.get_location()
self.protocol.defuse_loop = LoopingCall(self.protocol.defuse_action, self)
self.protocol.defuse_loop.start(0.08)
return ret
return False
def capture_flag(self): # event that is triggered when terrorist come near their base/tent (bomb site).
if not self.protocol.game_enabled or not self.protocol.match_running:
return connection.capture_flag(self)
if self.protocol.destruction and self.protocol.destruction.active() or self.protocol.plant_timer:
return False
if self.team == self.protocol.green_team:
if self.tool != BLOCK_TOOL:
self.send_chat("You must have your BLOCK TOOL out in order to plant the bomb!")
return False
send_chat(self, "I'm planting the bomb.", global_message=False)
self.protocol.plant_pos = self.get_location()
self.protocol.plant_loop = LoopingCall(self.protocol.plant_action, self)
self.protocol.plant_loop.start(0.08)
return False
class BombdefusalProtocol(protocol):
game_mode = CTF_MODE
def on_map_change(self, new_map):
extensions = self.map_info.extensions # checking if extensions are set properly
if not extensions:
self.game_enabled = False
self.match_running = False
return protocol.on_map_change(self, new_map)
self.building = False
self.green_team.spawn = extensions.get('t_spawn', out_of_map)
self.blue_team.spawn = extensions.get('ct_spawn', out_of_map)
self.bomb_site = extensions.get('bomb_site', out_of_map)
self.bomb_duration = extensions.get('bomb_duration', BOMB_TIME)
self.bomb_radius = extensions.get('bomb_radius', 40)
self.defuse_time = extensions.get('defuse_time', 5)
self.plant_time = extensions.get('plant_time', 5)
self.start_round(first=True)
return protocol.on_map_change(self, new_map)
def start_round(self, first=False, winner=False):
self.game_enabled = True # Whether or not Defusal should run on this map
self.match_running = False # Whether or not the round is running
if not first:
if self.destruction and self.destruction.active():
self.destruction.cancel()
if self.time_limit_check and self.time_limit_check.active():
self.time_limit_check.cancel()
self.green_team.flag.player.drop_flag() if self.green_team.flag.player else None # all objects are relocated back to their original place
self.green_team.flag.set(*out_of_map)
self.green_team.flag.update()
self.green_team.base.set(*self.bomb_site)
self.green_team.base.update()
self.blue_team.flag.player.drop_flag() if self.blue_team.flag.player else None
self.blue_team.flag.set(*self.green_team.spawn)
self.blue_team.flag.update()
self.blue_team.base.set(*out_of_map)
self.blue_team.base.update()
for team in (self.blue_team, self.green_team): # must have atleast one player in each team to start round
if team.count() == 0:
self.send_chat("Both teams require at least one player to start.")
callLater(5, self.start_round, first=True)
return
if winner:
self.win_action(winner=winner)
else:
self.match_running = True
if winner is None:
self.send_chat("Time ran out! No team wins.")
self.reset_everything()
self.match_running = True
self.send_chat("Begin round!")
self.send_chat("Time limit for this round: {} seconds.".format(ROUND_TIME))
self.time_limit_check = callLater(ROUND_TIME, self.start_round, winner=None)
self.defuser = None # Connection object of the bomb defuser
self.planter = None # Connection object of the bomb planter
self.plant_pos = (0, 0, 0) # Location in which the bomb was planted
self.plant_timer = 0.0 # Time since bomb plant began. (Reset to 0 after plant is over)
self.plant_loop = None # The twisted LoopingCall object that calls the plant_action function
self.defuse_pos = (0, 0, 0) # Location in which the bomb was defused
self.defuse_timer = 0.0 # Time since bomb defuse began. (Reset to 0 after defuse is over)
self.defuse_loop = None # The twisted LoopingCall object that calls the defuse_action function
self.add_delay = False # Whether to add delay after planting or defusing was cancelled.
self.destruction = None # The twisted callLater object that explodes the bomb if not cancelled.
def on_base_spawn(self, x, y, z, base, entity_id):
if not self.game_enabled:
return protocol.on_base_spawn(self, x, y, z, base, entity_id)
if entity_id == GREEN_BASE: # Base (tent) of green team (Terrorists) spawns at bomb site
return self.bomb_site
return out_of_map
def on_flag_spawn(self, x, y, z, flag, entity_id):
if not self.game_enabled:
return protocol.on_flag_spawn(self, x, y, z, flag, entity_id)
if entity_id == BLUE_FLAG: # Flag of blue team (Counter-Terrorists) spawns at Terrorist spawn location
return self.green_team.spawn
return out_of_map
def plant_action(self, connection): # Logic for planting bomb, this function is called in a loop with set intervals
if(self.add_delay):
time.sleep(0.8)
self.add_delay = False
self.current_defuser = connection
remaining = round(self.plant_time - self.plant_timer, 2)
time.sleep(0.08)
self.plant_timer += 0.08
connection.set_location(self.plant_pos)
try:
if remaining.is_integer():
connection.send_chat("planting bomb: {} seconds remaining. Sneak (Default: V) or change weapons to cancel.".format(remaining))
except:
remaining = float(remaining)
if remaining.is_integer():
connection.send_chat("planting bomb: {} seconds remaining. Sneak (Default: V) or change weapons to cancel.".format(remaining))
if connection and connection.world_object.sneak or connection.tool != BLOCK_TOOL:
self.stop_planting(canceled=True, connection=connection)
callLater(2, self.reset_status)
return
if self.plant_timer < self.plant_time:
return
self.stop_planting(canceled=False, connection=connection)
self.plant_success(connection)
def stop_planting(self, canceled, connection): # stop planting loop, either because canceled or successful
connection.planting = False
try:
self.plant_loop.stop()
except:
pass
if canceled:
self.add_delay = True
self.plant_pos = (0, 0, 0) if canceled else self.plant_pos # We use this later for the explosion.
self.plant_timer = 0
def plant_success(self, connection): # bomb is planted, bomb duration call is initiated
if self.destruction and self.destruction.active():
return
if self.time_limit_check and self.time_limit_check.active():
self.time_limit_check.cancel()
self.planter = connection
self.destruction = callLater(self.bomb_duration, self.explode_bomb) # explode function will be called after bomb_duration
self.send_chat("Bomb has been planted. {} seconds until detonation.".format(self.bomb_duration))
player_pos = connection.get_location()
self.green_team.flag.set(player_pos[0], player_pos[1], self.map.get_z(player_pos[0], player_pos[1])) # replace Green Team's tent with Green Team's flag at bomb site to represent bomb.
self.green_team.flag.update()
self.green_team.base.set(*out_of_map)
self.green_team.base.update()
self.blue_team.flag.set(*out_of_map)
self.blue_team.flag.update()
def explode_bomb(self): # bomb effect
if self.defuse_loop:
self.stop_defusing(canceled=True)
for _ in range(12):
x, y, z = self.bomb_site
position = Vertex3(x, y, z)
velocity = Vertex3(0.0, 0.0, 0.0)
grenade_object = self.world.create_object(Grenade, 0.0, position, None, velocity,
self.planter.grenade_exploded)
grenade_object.name = "c4"
grenade_packet.value = grenade_object.fuse
grenade_packet.player_id = self.planter.player_id
grenade_packet.position = position.get()
grenade_packet.velocity = velocity.get()
self.send_contained(grenade_packet)
self.match_running = False
self.send_chat("Terrorists win.")
for player in self.players.values():
damage = bomb_calc(self.plant_pos, player.get_location(), self.bomb_radius) * 100
if damage:
player.hit(value=damage, by=self.planter)
callLater(2, self.start_round, winner=self.green_team)
def defuse_action(self, connection): #logic for defusing bomb, this function is called in a loop at set intervals.
if(self.add_delay):
time.sleep(0.8)
self.add_delay = False
time.sleep(0.08)
remaining = round(self.defuse_time - self.defuse_timer, 1)
self.defuse_timer += 0.08
connection.set_location(self.defuse_pos)
try:
if remaining.is_integer():
connection.send_chat("Defusing bomb: {} seconds remaining. Sneak (Default: V) or change weapons to cancel.".format(remaining))
except:
remaining = float(remaining)
if remaining.is_integer():
connection.send_chat("Defusing bomb: {} seconds remaining. Sneak (Default: V) or change weapons to cancel.".format(remaining))
if connection and connection.world_object.sneak or connection.tool != BLOCK_TOOL:
self.stop_defusing(canceled=True, connection=connection)
callLater(2, self.reset_status) # if action is cancelled all cooldowns are reset
return
if self.defuse_timer < self.defuse_time:
return
try:
self.destruction.cancel()
except (AlreadyCancelled, AlreadyCalled):
return
self.defuser = connection
self.stop_defusing(canceled=False, connection=connection)
self.match_running = False
self.send_chat("Bomb has been defused. Counter-Terrorists win.")
self.send_chat("{player} defused the bomb.".format(player=self.defuser.name))
callLater(2, self.start_round, winner=self.blue_team)
def stop_defusing(self, canceled, connection=None): #stop defusing loop, either because canceled or successful
if connection:
connection.defusing = False
else:
self.current_defuser.defusing = False
self.current_defuser = None
try:
self.defuse_loop.stop()
except:
pass
if canceled:
self.add_delay = True
if canceled and self.green_team.flag.player: # if cancelled return the bomb (green intel) to its location
self.green_team.flag.player.drop_flag()
self.green_team.flag.set(self.plant_pos[0],self.plant_pos[1], self.map.get_z(self.plant_pos[0], self.plant_pos[1]))
self.green_team.flag.update()
self.defuse_pos = (0, 0, 0) if canceled else self.defuse_pos
self.defuse_timer = 0
def win_action(self, winner):
player = self.planter if winner == self.green_team else self.defuser
if not (player and player.team == winner):
player = random.choice(list(winner.get_players()))
if player.world_object.dead:
player.spawn(winner.spawn)
player.take_flag()
player.capture_flag()
def reset_everything(self): # relocate players to their spawn locations and refill everything. Also respawns all dead players
# Taken from arena.py
for player in self.players.values():
player.defusing = False
player.planting = False
if player.team.spectator:
continue
if player.world_object.dead:
player.spawn(player.team.spawn)
else:
player.set_location(player.team.spawn)
player.refill()
def check_game_status(self): # check if one of the conditions for ending the round is met
if not self.match_running:
return
if all_dead(self.green_team):
if self.destruction:
if self.destruction.active():
return
else:
try:
self.destruction.cancel()
except (AlreadyCancelled, AlreadyCalled):
return
self.match_running = False
self.send_chat("Conter-Terrorists win")
callLater(2, self.start_round, winner=self.blue_team)
elif all_dead(self.blue_team):
if self.destruction:
try:
self.destruction.cancel()
except (AlreadyCalled, AlreadyCancelled):
return
self.match_running = False
self.send_chat("Terrorists win.")
callLater(2, self.start_round, winner=self.green_team)
def reset_status(self): # reset timers
self.plant_timer = 0
self.defuse_timer = 0
return BombdefusalProtocol, BombdefusalConnection
[code=map extensions]
extensions = {
‘ct_spawn’: (235, 310, 58),
‘t_spawn’: (275, 180, 49),
‘bomb_site’: (205, 307, 52),
}
---