[GAME MODE] Halo Style Infection

Incomplete code that isn't ready for use.
6 posts Page 1 of 1
MindSpunk
Deuce
Posts: 6
Joined: Wed Jun 24, 2015 9:13 am


After spending many, MANY hours playing Halo: Reach Infection mode I was lead to create that style of game in Ace of Spades. And that's what I have done here. The aim of this game-mode is to replicate, to some extent the gameplay of Halo Infection. For those that don't know what that is...
  • Humans are armed with their guns and grenades, while zombies can only damage players with the spade. Nobody can build or break blocks.
  • The goal for the humans is to survive for the length of one round (configurable)
  • The goal for the zombies is to kill all humans
  • When a zombie kills a human they are "infected" and respawn as a zombie. The zombies win when there are no humans left, while humans win if there are any humans alive at the end of the round
Zombies have configurable damage resistance to the different weapons.

The main advantage zombies have is the ability to "lunge" when they press the sneak button (default bound to 'v'). I had to modify Dr.Morphman's "Hookshot" to only allow zombies to use it (Dr.Morphman's Script: http://buildandshoot.com/viewtopic.php?f=67&t=9817).
As such I am redistributing his script with my modifications, so to make it clear HOOKSHOT.PY IS NOT MY OWN WORK.

There are some requirements for metadata to be placed in the map.txt file for the map you are using. In the extensions tag you must place:
  • An entry called "infection" and it's value must be True
  • An entry called "zombie_spawns" that contains a series of tuples specifying coordinates for zombie spawns. THERE MUST BE AT LEAST 2.
  • An entry called "human_spawn" that contains a SINGLE tuple specifying the coordinates for the human's spawn
An example file shown below using the map Desert_Combat as a basis.
Code: Select all
name = 'Desert_Combat'
version = '0.1'
author = 'unknown'
description = 'korea sever'

from pyspades.constants import *
from pyspades.server import ServerConnection
def get_entity_location(team, entity_id):
    if entity_id == BLUE_FLAG:        
        z = team.protocol.map.get_z(0, 0)
        return (0, 0, z)
def get_spawn_location(connection):
    if connection.name == '':
        x, y, z = ServerConnection.get_spawn_location(connection)
        return (x, y, z)    
    if connection.team is connection.protocol.blue_team:
        x, y, z = ServerConnection.get_spawn_location(connection)
        return (281, 274, 58)
    if connection.team is connection.protocol.green_team:
        x, y, z = ServerConnection.get_spawn_location(connection)
        return (435, 263, 58)

def get_entity_location(team, entity_id):

    if entity_id == BLUE_FLAG:

        return (435, 254, 58)

    if entity_id == GREEN_FLAG:

        return (281, 263, 58)

    if entity_id == BLUE_BASE:

        return (435, 280, 58)

    if entity_id == GREEN_BASE:

        return (281, 272, 58)


extensions = { 
				'water_damage' : 50,
				'infection' : True,
				'zombie_spawns' : ((281,263,58),(281,263,58)),
				'human_spawn' : (435,254,58)
			}
I will be making maps for this gamemode myself, which I will post in the maps section and update this with links to them.

Maps
  • None yet :p
Attachments
infect.py
This is to be set as the server gamemode. This is my work
(11.82 KiB) Downloaded 120 times
hookshot.py
This is to be set as a server script. This is NOT my work
(4.03 KiB) Downloaded 109 times
Last edited by MindSpunk on Mon Jan 11, 2016 12:31 pm, edited 2 times in total.
LeCom
Post Demon
Post Demon
Posts: 866
Joined: Sat May 24, 2014 8:07 am


1. Instead of connection.send_chat(xyz), I suggest return xyz; in case you want to make it work on IRC (and console I think)
2. Instead of protocol.whateverteam.get_players(), use protocol.players.values()
3. Maybe say what exactly is broken? This helps a lot.
MindSpunk
Deuce
Posts: 6
Joined: Wed Jun 24, 2015 9:13 am


Thanks for the reply

At the moment most of it doesn't work coherently. The main issue is with the automating the gamemode, and setting it up as an actual gamemode and not something just running as a script. I'm not quite sure to what extent it is broken because I haven't really been able to test it with more than 2 clients, so I'm just going to list what doesn't work at the moment.
  • There is no prevention of players killing each other or interacting with the map while the lobby is running. This is because my implementation didn't work properly. It was supposed to be a simple if statement checking if a value was true then returning false. But I couldn't get it to change the variables when the round starts
  • Killing players magically stopped working, for some reason. I had to put a self.kill() in the on_kill hook for it to actually kill the player, but it seems to work for now
  • The game requires me to input commands to start the lobby, from then on it is automated until the next map loads at the end of the round. I just haven't figured out a way to make it actually run itself
This is the first time I have ever written something in Python, so it probably won't be very neat :|

Thanks for reading
-MindSpunk
LeCom
Post Demon
Post Demon
Posts: 866
Joined: Sat May 24, 2014 8:07 am


  • In your current script, it only disables map interaction when you're a zombie. If you mean you can't change match_running, you can't change global variables (variables put outside of any class or function) normally. You either need to put it inside a class (like protocol) or use something like:
    Code: Select all
        match_running=False
        def something():
            global match_running
            match_running=True
    
    If you want to prevent people from killing each other, return False or 0 in on_kill. Or better, return 0 in on_hit.
  • The on_kill code does not look wrong or whatever. Maybe it does not do the "return connection.on_kill(...)" part?
  • IIRC protocol.on_map_change is also called just after the server starts. If not, protocol.__init__(self, *arg, **kwarg) is useful for the first time. It is called while the server intializes. However, that moment is too early for anything to work, so I usually put a callLater set to 1 ms or so, and pointing to the function I want to hook in. While using this __init__(...), don't forget to return properly!
Your code is fine btw, I just recommend putting spaces after commas, because without spaces, it looks ugly.
MindSpunk
Deuce
Posts: 6
Joined: Wed Jun 24, 2015 9:13 am


So... I'm back from the dead.

After posting 2 times then vanishing, I return. 6 Months later and I bear the gifts of server scripts.

So, I've *nearly* finished the infection script. I completely re-wrote it so it was a bit more legible and now actually works :D. I've removed the humans ability to heal each other as I feel that was rather OP and ruined some of the fun of surviving by 3 health. Also there have been some changes to how spawning works. Now you must specify spawns in the map.txt file in the extensions part of the file. I will post an example file so you can see what has to be done. But the big one. It's completely automated. It will run through rounds and assign the starting zombie automatically, completely hands off. There is only one thing left to do, and that is to have the plugin tell users how to play (tell them they are human/zombie, tell them what they can/can't do, etc). But beyond that the script works, although I am yet to stress test it in a live server environment.

So yeah, enjoy.

Here's the code for the MAP.txt File (I used Desert_Combat for testing the script)
Code: Select all
name = 'Desert_Combat'
version = '0.1'
author = 'unknown'
description = 'korea sever'

from pyspades.constants import *
from pyspades.server import ServerConnection
def get_entity_location(team, entity_id):
    if entity_id == BLUE_FLAG:        
        z = team.protocol.map.get_z(0, 0)
        return (0, 0, z)
def get_spawn_location(connection):
    if connection.name == '':
        x, y, z = ServerConnection.get_spawn_location(connection)
        return (x, y, z)    
    if connection.team is connection.protocol.blue_team:
        x, y, z = ServerConnection.get_spawn_location(connection)
        return (281, 274, 58)
    if connection.team is connection.protocol.green_team:
        x, y, z = ServerConnection.get_spawn_location(connection)
        return (435, 263, 58)

def get_entity_location(team, entity_id):

    if entity_id == BLUE_FLAG:

        return (435, 254, 58)

    if entity_id == GREEN_FLAG:

        return (281, 263, 58)

    if entity_id == BLUE_BASE:

        return (435, 280, 58)

    if entity_id == GREEN_BASE:

        return (281, 272, 58)


extensions = { 
				'water_damage' : 50,
				'infection' : True,
				'zombie_spawns' : ((281,263,58),(281,263,58)),
				'human_spawn' : (435,254,58)
			}
you will notice that the two 'zombie_spawns' tuples are the same, I only wanted to specify a single spawn and my code only works if there is more than one entry in the zombie_spawns part. Shoot me, I didn't really want to fix something that wasn't completely broken and if you have only one zombie spawn in an actual map you're doing it wrong.

And here's the code for what I have so far, I'll update the attachment in the original post when I finish the last bit I need to do
Code: Select all
from pyspades.constants import *
from commands import add, admin, alias
from twisted.internet.task import LoopingCall
from twisted.internet import reactor
import random

#CONSTANTS - DON'T TOUCH THESE
HUMAN = 0
ZOMBIE = 1
HIDE_COORD = (0,0,63)

#CONFIGURABLES
LOBBY_TIME = 60 #The amount of time the lobby lasts in seconds
ROUND_TIME = 180 #The amount of time a round lasts in seconds
ZOMBIE_RIFLE_RESIST = 2 #Divisor for how much damage a zombie takes against rifle
ZOMBIE_SMG_RESIST = 5 #Divisor for how much damage a zombie takes against smg
ZOMBIE_SHOTGUN_RESIST = 2 #Divisor for how much damage a zombie takes against shotgun
ZOMBIE_SHOVEL_RESIST = 10 #Divisor for how much damage a zombie takes against shovel
ZOMBIE_HIT_PENALTY = 3 #Divisor for how much damage a zombie does with shovel
Z_FALL_IMMUNE = True #Are zombies immune to fall damage


## FUNCTIONS
def startMatch(protocol):

	protocol.MATCH_RUNNING = True
	
def stopMatch(protocol):

	protocol.MATCH_RUNNING = False


def randomSpawn(spawns):

	i = random.randint(0,(len(spawns)-1))
	return spawns[i].getSpawnPosition()
	
def getTeamDead(team):
	for player in team.get_players():
		if player.state == HUMAN:
			return False
	return True
	
def roundWin(killer):

	if killer != None:

		killer.take_flag()
		killer.capture_flag()
		
def chooseZombie(protocol):

	for player in protocol.blue_team.get_players():
		protocol.PLAYER_LIST.append(player)
	for player in protocol.green_team.get_players():
		protocol.PLAYER_LIST.append(player)
		
	i = random.randint(0,len(protocol.PLAYER_LIST)-1)
		
	protocol.PLAYER_LIST[i].state = ZOMBIE
	protocol.send_chat(protocol.PLAYER_LIST[i].name + " is the starting zombie")
	
def setAllHuman(protocol):

	for player in protocol.blue_team.get_players():
		protocol.PLAYER_LIST.append(player)
	for player in protocol.green_team.get_players():
		protocol.PLAYER_LIST.append(player)
	
	for i in xrange(0, len(protocol.PLAYER_LIST)-1):
		protocol.PLAYER_LIST[i].state = HUMAN
		
## FUNCTIONS

## CLASSES
class ZombieSpawn:
	
	def __init__(self,x,y,z):
		self.spawn = (x,y,z)
		
	def getSpawnPosition(self):
		return self.spawn
		
class HumanSpawn:

	def __init__(self,x,y,z):
		self.spawn = (x,y,z)
	
	def getSpawnPosition(self):
		return self.spawn
## CLASSES


## COMMANDS	

#Get if MATCH_RUNNING is true or false
@alias("gs")
@admin
def getstatus(connection):

	if connection.protocol.MATCH_RUNNING:
		connection.send_chat("Match Running")
	elif not connection.protocol.MATCH_RUNNING:
		connection.send_chat("Match Not Running")
add(getstatus)

#Get you current infection status
@alias("state")
@admin
def getstate(connection):

	if connection.state == HUMAN:
		connection.send_chat("You are a human")
	elif connection.state == ZOMBIE:
		connection.send_chat("You are a zombie")
add(getstate)

#Set your state to zombie
@alias("zom")
@admin
def becomezombie(connection):
	
	connection.state = ZOMBIE
	connection.send_chat("You have become a zombie")
add(becomezombie)

#Set your state to human
@alias("hum")
@admin
def becomehuman(connection):

	connection.state = HUMAN
	connection.send_chat("You have become a human")
add(becomehuman)

#Set MATCH_RUNNING true
@alias("sr")
@admin
def startround(connection):
	startMatch(connection.protocol)
	connection.send_chat("Setting MATCH_RUNNING to true")
add(startround)

#Set MATCH_RUNNING false
@alias("er")
@admin
def endround(connection):
	stopMatch(connection.protocol)
	connection.send_chat("Setting MATCH_RUNNING to false")
add(endround)

#Pick zombies
@alias("pz")
@admin
def pickzombies(connection):
	chooseZombie(connection.protocol)
add(pickzombies)
## COMMANDS

def apply_script(protocol, connection, config):

	class InfConnection(connection):
		
		def on_join(self):
			
			#If a match is running, people who join become zombies
			if self.protocol.MATCH_RUNNING:
				self.state = ZOMBIE
			else:
				self.state = HUMAN
				
			return connection.on_join(self)
			
		
		def on_spawn_location(self, pos):
		
			if not self.protocol.MATCH_RUNNING:
				setAllHuman(self.protocol)
		
			ext = self.protocol.map_info.extensions
			
			#Humans on humans team, Zombies on zombie team
			if self.protocol.MATCH_RUNNING:
				if self.state == HUMAN:
					self.team = self.protocol.blue_team
				elif self.state == ZOMBIE:
					self.team = self.protocol.green_team
			
			#While match is running place people in appropriate spawn locations
			if self.protocol.MATCH_RUNNING:
			
				if self.state == ZOMBIE:
					return randomSpawn(self.protocol.zombieSpawns)
					
				elif self.state == HUMAN:
					return self.protocol.humanSpawn
					
					
		def on_hit(self, hit_amount, hit_player, type, grenade):
			
			#Stop damage if match is not running
			if not self.protocol.MATCH_RUNNING:
				return False
			else:
				
				#ZOMBIE DAMAGE MODIFIERS
				if self.state == HUMAN and hit_player.state == ZOMBIE:
					
					if self.tool == WEAPON_TOOL:
					
						if self.weapon == RIFLE_WEAPON:
							return hit_amount/ZOMBIE_RIFLE_RESIST
						elif self.weapon == SMG_WEAPON:
							return hit_amount/ZOMBIE_SMG_RESIST
						elif self.weapon == SHOTGUN_WEAPON:
							return hit_amount/ZOMBIE_SHOTGUN_RESIST
							
							
					elif self.tool == SPADE_TOOL:
						return hit_amount/ZOMBIE_SHOVEL_RESIST
				
				#HUMAN DAMAGE MODIFIERS
				elif self.state == ZOMBIE and hit_player.state == HUMAN:
					
					if self.tool == SPADE_TOOL:
						return hit_amount/ZOMBIE_HIT_PENALTY
					else: 
						return False
				
				else:
					
					return connection.on_hit(self, hit_amount, hit_player, type, grenade)
					
			
		def on_kill(self, killer, type, grenade):
		
			#If a match is running, handle player infections
			if self.protocol.MATCH_RUNNING:
			
				if self.state == HUMAN:
					
					if killer != None:
					
						if killer.state == ZOMBIE:
					
							self.state = ZOMBIE
							self.send_chat("You have been infected")
							killer.send_chat("You infected " + self.name)
						
			self.protocol.check_round_end(killer)
			print("CHECKING ROUND END")
			
			return connection.on_kill(self, killer, type, grenade)
			
		def on_fall(self, damage):
		
			if not self.protocol.MATCH_RUNNING:
				return False
			
			if self.protocol.MATCH_RUNNING:
				if self.state == ZOMBIE:
					return False
				else:
					return connection.on_fall(self, damage)
			
				
		def on_block_build_attempt(self, x, y, z):
			
			#Stop building
			return False
			
		
		def on_block_destroy(self, x, y, z, mode):
		
			#Stop block destruction
			return False
			
			
		def on_grenade(self, time_left):
		
			#Stop zombies using grenade
			if self.state == ZOMBIE:
				return False
			else:
				return connection.on_grenade(self, time_left)
			
			
	class InfProtocol(protocol):
		
		game_mode = CTF_MODE
		
		def on_advance(self, map):
		
			self.PLAYER_LIST = []
		
			setAllHuman(self)
			
			self.countdown = reactor.callLater(LOBBY_TIME, self.lobbyTimer)

		
		def lobbyTimer(self):
		
			count = 0
		
			for team in (self.green_team, self.blue_team):
				count += team.count()
				
			if count >= 2:
				self.beginRound()
			else:
				self.send_chat("A GAME REQUIRES ATLEAST 2 PEOPLE")
				print("NOT ENOUGH PLAYERS")
				self.countdown = reactor.callLater(LOBBY_TIME, self.lobbyTimer)
			
		
		def on_map_change(self, map):
		
			self.PLAYER_LIST = []
		
			self.MATCH_RUNNING = False
		
			if self.map_info.extensions['infection']:
				
				ext = self.map_info.extensions
				self.zombieSpawns = []
				self.humanSpawn = ext['human_spawn']
				
				for spawns in ext['zombie_spawns']:
					self.zombieSpawns.append(ZombieSpawn(*spawns))
					
		def check_round_end(self, killer = None, message = True):
		
			if self.MATCH_RUNNING:
				
				if getTeamDead(self.blue_team):
					self.winRound(killer)
					return
				
		
		def winRound(self, killer):
			
			if killer.state == ZOMBIE:
				self.roundTimer.cancel()
		
			print("ROUND WON")
			self.MATCH_RUNNING = False
			roundWin(killer)
			setAllHuman(self)
			self.MATCH_RUNNING = False
			return
			
		def beginRound(self):
		
			chooseZombie(self)
			self.roundTimer = reactor.callLater(ROUND_TIME, self.humanWin)
		
			for player in self.PLAYER_LIST:
				player.kill()
			
			self.MATCH_RUNNING = True
		
		def humanWin(self):
		
			for player in self.blue_team.get_players():
				if player.state == HUMAN:
					self.winRound(player)
					return
			
		def on_base_spawn(self, x, y, z, base, entity_id):
			return HIDE_COORD

		def on_flag_spawn(self, x, y, z, flag, entity_id):
			return HIDE_COORD

	return InfProtocol, InfConnection or None
MindSpunk
Deuce
Posts: 6
Joined: Wed Jun 24, 2015 9:13 am


UPDATED MAIN POST WITH COMPLETED SCRIPT
6 posts Page 1 of 1
Return to “Work In Progress”

Who is online

Users browsing this forum: No registered users and 1 guest