From 349204e38f958d0a1da53db40f632da4d1d289f1 Mon Sep 17 00:00:00 2001 From: Yourself Date: Tue, 10 Jul 2012 17:50:47 -0500 Subject: [PATCH 01/36] Fog flashes team color in arena on round end --- contrib/scripts/arena.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/contrib/scripts/arena.py b/contrib/scripts/arena.py index ef112f9b..0adee6ec 100644 --- a/contrib/scripts/arena.py +++ b/contrib/scripts/arena.py @@ -36,6 +36,7 @@ from twisted.internet import reactor from twisted.internet.task import LoopingCall from commands import add, admin +import random # If ALWAYS_ENABLED is False, then the 'arena' key must be set to True in # the 'extensions' dictionary in the map metadata @@ -261,10 +262,12 @@ def on_team_join(self, team): return returned def get_respawn_time(self): - if self.protocol.arena_running: - return -1 - else: - return 1 + if self.protocol.arena_enabled: + if self.protocol.arena_running: + return -1 + else: + return 1 + return connection.get_respawn_time(self); def respawn(self): if self.protocol.arena_running: @@ -272,6 +275,8 @@ def respawn(self): return connection.respawn(self) def on_spawn(self, pos): + print self.team.color + self.protocol.set_fog_color(self.team.color) returned = connection.on_spawn(self, pos) if self.protocol.arena_running: self.kill() From 42a64ca5f76fc21672e16dddf769efb2fdfc6a6c Mon Sep 17 00:00:00 2001 From: Yourself Date: Tue, 10 Jul 2012 17:52:15 -0500 Subject: [PATCH 02/36] Actually add fog color --- contrib/scripts/arena.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/contrib/scripts/arena.py b/contrib/scripts/arena.py index 0adee6ec..8b2de782 100644 --- a/contrib/scripts/arena.py +++ b/contrib/scripts/arena.py @@ -45,6 +45,9 @@ # How long should be spent between rounds in arena (seconds) SPAWN_ZONE_TIME = 20.0 +# How many seconds a team color should be shown after they win a round +TEAM_COLOR_TIME = 4.0 + # Maximum duration that a round can last. Time is in seconds. Set to 0 to # disable the time limit MAX_ROUND_TIME = 180 @@ -275,8 +278,6 @@ def respawn(self): return connection.respawn(self) def on_spawn(self, pos): - print self.team.color - self.protocol.set_fog_color(self.team.color) returned = connection.on_spawn(self, pos) if self.protocol.arena_running: self.kill() @@ -309,6 +310,7 @@ class ArenaProtocol(protocol): arena_take_flag = False arena_countdown_timers = None arena_limit_timer = None + arena_old_fog_color = None def check_round_end(self, killer = None, message = True): if not self.arena_running: @@ -345,9 +347,16 @@ def arena_win(self, team, killer = None): self.arena_take_flag = True killer.take_flag() killer.capture_flag() + self.arena_old_fog_color = self.fog_color + self.set_fog_color(team.color) + reactor.callLater(TEAM_COLOR_TIME, self.arena_reset_fog_color) self.send_chat(team.name + ' team wins the round!') self.begin_arena_countdown() + def arena_reset_fog_color(self): + if self.arena_old_fog_color is not None: + self.set_fog_color(self.arena_old_fog_color) + def arena_remaining_message(self): if not self.arena_running: return @@ -401,6 +410,7 @@ def on_map_change(self, map): self.arena_running = False self.arena_counting_down = False self.arena_limit_timer = None + self.arena_old_fog_color = None self.old_respawn_time = None self.old_building = None self.old_killing = None From 6f912001fd386ef06decd6f3f539376e98ef1c31 Mon Sep 17 00:00:00 2001 From: Yourself Date: Tue, 10 Jul 2012 19:38:47 -0500 Subject: [PATCH 03/36] Add option for multiple spawn locations for teams in arena --- contrib/scripts/arena.py | 43 +++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/contrib/scripts/arena.py b/contrib/scripts/arena.py index 8b2de782..b0a29b48 100644 --- a/contrib/scripts/arena.py +++ b/contrib/scripts/arena.py @@ -13,19 +13,24 @@ # Spawn locations and gate locations MUST be present in the map metadata (map txt file) # for arena to work properly. -# Spawn locations for the green team are set by using the data from the 'arena_green_spawn' -# tuple in the extensions dictionary. Likewise, the blue spawn is set with the 'arena_blue_spawn' -# key. +# The spawn location/s for the green team are set by using the data from the 'arena_green_spawns' +# tuple in the extensions dictionary. Likewise, the blue spawn/s is set with the 'arena_blue_spawns' +# key. 'arena_green_spawns' and 'arena_blue_spawns' are tuples which contain tuples of spawn +# coordinates. Spawn locations are chosen randomly. + +# NOTE THAT THE SCRIPT RETAINS BACKWARDS COMPATIBILITY with the old 'arena_green_spawn' and +# 'arena_blue_spawn' # The locations of gates is also determined in the map metadata. 'arena_gates' is a # tuple of coordinates in the extension dictionary. Each gate needs only one block # to be specified (since each gate is made of a uniform color) # Sample extensions dictionary of an arena map with two gates: +# In this example there is one spawn location for blue and two spawn locations for green. # extensions = { # 'arena': True, -# 'arena_blue_spawn' : (128, 256, 60), -# 'arena_green_spawn' : (384, 256, 60), +# 'arena_blue_spawns' : ((128, 256, 60),), +# 'arena_green_spawns' : ((384, 256, 60), (123, 423, 51)), # 'arena_gates': ((192, 236, 59), (320, 245, 60)) # } @@ -46,6 +51,7 @@ SPAWN_ZONE_TIME = 20.0 # How many seconds a team color should be shown after they win a round +# Set to 0 to disable this feature. TEAM_COLOR_TIME = 4.0 # Maximum duration that a round can last. Time is in seconds. Set to 0 to @@ -285,7 +291,7 @@ def on_spawn(self, pos): def on_spawn_location(self, pos): if self.protocol.arena_enabled: - return self.team.arena_spawn + return random.choice(self.team.arena_spawns) return connection.on_spawn_location(self, pos) def on_flag_take(self): @@ -348,8 +354,9 @@ def arena_win(self, team, killer = None): killer.take_flag() killer.capture_flag() self.arena_old_fog_color = self.fog_color - self.set_fog_color(team.color) - reactor.callLater(TEAM_COLOR_TIME, self.arena_reset_fog_color) + if TEAM_COLOR_TIME > 0: + self.set_fog_color(team.color) + reactor.callLater(TEAM_COLOR_TIME, self.arena_reset_fog_color) self.send_chat(team.name + ' team wins the round!') self.begin_arena_countdown() @@ -388,14 +395,18 @@ def on_map_change(self, map): if extensions.has_key('arena_gates'): for gate in extensions['arena_gates']: self.gates.append(Gate(*gate, protocol_obj=self)) - if extensions.has_key('arena_green_spawn'): - self.green_team.arena_spawn = extensions['arena_green_spawn'] + if extensions.has_key('arena_green_spawns'): + self.green_team.arena_spawns = extensions['arena_green_spawns'] + elif extensions.has_key('arena_green_spawn'): + self.green_team.arena_spawns = (extensions['arena_green_spawn'],) else: - raise CustomException('No arena_green_spawn given in map metadata.') - if extensions.has_key('arena_blue_spawn'): - self.blue_team.arena_spawn = extensions['arena_blue_spawn'] + raise CustomException('No arena_green_spawns given in map metadata.') + if extensions.has_key('arena_blue_spawns'): + self.blue_team.arena_spawns = extensions['arena_blue_spawns'] + elif extensions.has_key('arena_blue_spawn'): + self.blue_team.arena_spawns = (extensions['arena_blue_spawn'],) else: - raise CustomException('No arena_blue_spawn given in map metadata.') + raise CustomException('No arena_blue_spawns given in map metadata.') self.delay_arena_countdown(MAP_CHANGE_DELAY) self.begin_arena_countdown() else: @@ -429,9 +440,9 @@ def arena_spawn(self): if player.team.spectator: continue if player.world_object.dead: - player.spawn(player.team.arena_spawn) + player.spawn(random.choice(player.team.arena_spawns)) else: - player.set_location(player.team.arena_spawn) + player.set_location(random.choice(player.team.arena_spawns)) player.refill() def begin_arena_countdown(self): From 02a2b7a7f2da957cd491fae64fc151df26d8a28f Mon Sep 17 00:00:00 2001 From: Yourself Date: Tue, 10 Jul 2012 19:45:19 -0500 Subject: [PATCH 04/36] Change default arena values --- contrib/scripts/arena.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/contrib/scripts/arena.py b/contrib/scripts/arena.py index b0a29b48..9b99f457 100644 --- a/contrib/scripts/arena.py +++ b/contrib/scripts/arena.py @@ -48,7 +48,7 @@ ALWAYS_ENABLED = True # How long should be spent between rounds in arena (seconds) -SPAWN_ZONE_TIME = 20.0 +SPAWN_ZONE_TIME = 15.0 # How many seconds a team color should be shown after they win a round # Set to 0 to disable this feature. @@ -58,7 +58,9 @@ # disable the time limit MAX_ROUND_TIME = 180 -MAP_CHANGE_DELAY = 30.0 +# Additional delay that is used while changing maps to allow players time +# to load the new map +MAP_CHANGE_DELAY = 25.0 # Coordinates to hide the tent and the intel HIDE_COORD = (0, 0, 63) From 871db6c4a8df0571c4aac3871a00425199a8f3ce Mon Sep 17 00:00:00 2001 From: Yourself Date: Tue, 10 Jul 2012 20:46:16 -0500 Subject: [PATCH 05/36] Arena bugfixes --- contrib/scripts/arena.py | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/contrib/scripts/arena.py b/contrib/scripts/arena.py index 9b99f457..7ef8f04e 100644 --- a/contrib/scripts/arena.py +++ b/contrib/scripts/arena.py @@ -42,13 +42,14 @@ from twisted.internet.task import LoopingCall from commands import add, admin import random +import math # If ALWAYS_ENABLED is False, then the 'arena' key must be set to True in # the 'extensions' dictionary in the map metadata ALWAYS_ENABLED = True # How long should be spent between rounds in arena (seconds) -SPAWN_ZONE_TIME = 15.0 +SPAWN_ZONE_TIME = 20.0 # How many seconds a team color should be shown after they win a round # Set to 0 to disable this feature. @@ -56,21 +57,25 @@ # Maximum duration that a round can last. Time is in seconds. Set to 0 to # disable the time limit -MAX_ROUND_TIME = 180 +#MAX_ROUND_TIME = 180 +MAX_ROUND_TIME = 30 -# Additional delay that is used while changing maps to allow players time -# to load the new map -MAP_CHANGE_DELAY = 25.0 +MAP_CHANGE_DELAY = 30.0 # Coordinates to hide the tent and the intel HIDE_COORD = (0, 0, 63) +# Max distance a player can be from a spawn while the players are held within +# the gates. If they get outside this they are teleported to a spawn. +# Used to teleport players who glitch through the map back into the spawns. +MAX_SPAWN_DISTANCE = 15.0 + BUILDING_ENABLED = False if MAX_ROUND_TIME >= 60: MAX_ROUND_TIME_TEXT = '%.2f minutes' % (float(MAX_ROUND_TIME)/60.0) else: - MAX_ROUND_TIME_TEXT = MAX_ROUND_TIME + ' seconds' + MAX_ROUND_TIME_TEXT = str(MAX_ROUND_TIME) + ' seconds' @admin def coord(connection): @@ -271,6 +276,22 @@ def on_team_join(self, team): self.world_object.dead = True self.protocol.check_round_end() return returned + + def on_position_update(self): + if not self.protocol.arena_running: + min_distance = None + pos = self.world_object.position + for spawn in self.team.arena_spawns: + xd = spawn[0] - pos.x + yd = spawn[1] - pos.y + zd = spawn[2] - pos.z + distance = math.sqrt(xd ** 2 + yd ** 2 + zd ** 2) + if min_distance is None or distance < min_distance: + min_distance = distance + if min_distance > MAX_SPAWN_DISTANCE: + self.set_location(random.choice(self.team.arena_spawns)) + self.refill() + return connection.on_position_update(self) def get_respawn_time(self): if self.protocol.arena_enabled: @@ -349,8 +370,9 @@ def arena_win(self, team, killer = None): return if killer is None or killer.team is not team: for player in team.get_players(): - killer = player - break + if not player.world_object.dead: + killer = player + break if killer is not None: self.arena_take_flag = True killer.take_flag() From 6d6316df447d9c6e8740568e4b22c17e7270aae1 Mon Sep 17 00:00:00 2001 From: Yourself Date: Tue, 10 Jul 2012 20:47:47 -0500 Subject: [PATCH 06/36] Fix arena defaults again --- contrib/scripts/arena.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/contrib/scripts/arena.py b/contrib/scripts/arena.py index 7ef8f04e..8fad452c 100644 --- a/contrib/scripts/arena.py +++ b/contrib/scripts/arena.py @@ -49,7 +49,7 @@ ALWAYS_ENABLED = True # How long should be spent between rounds in arena (seconds) -SPAWN_ZONE_TIME = 20.0 +SPAWN_ZONE_TIME = 15.0 # How many seconds a team color should be shown after they win a round # Set to 0 to disable this feature. @@ -57,10 +57,9 @@ # Maximum duration that a round can last. Time is in seconds. Set to 0 to # disable the time limit -#MAX_ROUND_TIME = 180 -MAX_ROUND_TIME = 30 +MAX_ROUND_TIME = 180 -MAP_CHANGE_DELAY = 30.0 +MAP_CHANGE_DELAY = 25.0 # Coordinates to hide the tent and the intel HIDE_COORD = (0, 0, 63) From a22136b7e2666249f1927f7aa5aff1ef1e90336c Mon Sep 17 00:00:00 2001 From: Yourself Date: Tue, 10 Jul 2012 20:59:57 -0500 Subject: [PATCH 07/36] Added map extensions arena_max_spawn_distance option --- contrib/scripts/arena.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/contrib/scripts/arena.py b/contrib/scripts/arena.py index 8fad452c..a2d8f31f 100644 --- a/contrib/scripts/arena.py +++ b/contrib/scripts/arena.py @@ -21,6 +21,9 @@ # NOTE THAT THE SCRIPT RETAINS BACKWARDS COMPATIBILITY with the old 'arena_green_spawn' and # 'arena_blue_spawn' +# The 'arena_max_spawn_distance' can be used to set MAX_SPAWN_DISTANCE on a map by map +# basis. See the comment by MAX_SPAWN_DISTANCE for more information + # The locations of gates is also determined in the map metadata. 'arena_gates' is a # tuple of coordinates in the extension dictionary. Each gate needs only one block # to be specified (since each gate is made of a uniform color) @@ -287,7 +290,7 @@ def on_position_update(self): distance = math.sqrt(xd ** 2 + yd ** 2 + zd ** 2) if min_distance is None or distance < min_distance: min_distance = distance - if min_distance > MAX_SPAWN_DISTANCE: + if min_distance > self.protocol.arena_max_spawn_distance: self.set_location(random.choice(self.team.arena_spawns)) self.refill() return connection.on_position_update(self) @@ -339,6 +342,7 @@ class ArenaProtocol(protocol): arena_countdown_timers = None arena_limit_timer = None arena_old_fog_color = None + arena_max_spawn_distance = MAX_SPAWN_DISTANCE def check_round_end(self, killer = None, message = True): if not self.arena_running: @@ -409,6 +413,7 @@ def on_map_change(self, map): self.arena_enabled = extensions['arena'] else: self.arena_enabled = False + self.arena_max_spawn_distance = MAX_SPAWN_DISTANCE if self.arena_enabled: self.old_respawn_time = self.respawn_time self.respawn_time = 0 @@ -430,6 +435,8 @@ def on_map_change(self, map): self.blue_team.arena_spawns = (extensions['arena_blue_spawn'],) else: raise CustomException('No arena_blue_spawns given in map metadata.') + if extensions.has_key('arena_max_spawn_distance'): + self.arena_max_spawn_distance = extensions['arena_max_spawn_distance'] self.delay_arena_countdown(MAP_CHANGE_DELAY) self.begin_arena_countdown() else: From 2071735291266ad6067ad9ce3162c674c2d612c3 Mon Sep 17 00:00:00 2001 From: Yourself Date: Wed, 11 Jul 2012 21:45:09 -0500 Subject: [PATCH 08/36] Arena fog fixes --- contrib/scripts/arena.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/contrib/scripts/arena.py b/contrib/scripts/arena.py index a2d8f31f..13a803ed 100644 --- a/contrib/scripts/arena.py +++ b/contrib/scripts/arena.py @@ -371,6 +371,11 @@ def arena_time_limit(self): def arena_win(self, team, killer = None): if not self.arena_running: return + if self.arena_old_fog_color is None: + self.arena_old_fog_color = self.fog_color + if TEAM_COLOR_TIME > 0: + self.set_fog_color(team.color) + reactor.callLater(TEAM_COLOR_TIME, self.arena_reset_fog_color) if killer is None or killer.team is not team: for player in team.get_players(): if not player.world_object.dead: @@ -380,16 +385,15 @@ def arena_win(self, team, killer = None): self.arena_take_flag = True killer.take_flag() killer.capture_flag() - self.arena_old_fog_color = self.fog_color - if TEAM_COLOR_TIME > 0: - self.set_fog_color(team.color) - reactor.callLater(TEAM_COLOR_TIME, self.arena_reset_fog_color) self.send_chat(team.name + ' team wins the round!') self.begin_arena_countdown() def arena_reset_fog_color(self): if self.arena_old_fog_color is not None: + # Shitty fix for disco on game end + self.old_fog_color = self.arena_old_fog_color self.set_fog_color(self.arena_old_fog_color) + self.arena_old_fog_color = None def arena_remaining_message(self): if not self.arena_running: From 75bb02cf3865cedb827076d12867ea5c5879fa87 Mon Sep 17 00:00:00 2001 From: matpow2 Date: Sat, 14 Jul 2012 02:29:33 +0200 Subject: [PATCH 09/36] Fix issue 266, changed 'team locked/full' behaviour to join spectator team --- pyspades/server.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyspades/server.py b/pyspades/server.py index 322def0f..0fb956fe 100644 --- a/pyspades/server.py +++ b/pyspades/server.py @@ -235,9 +235,10 @@ def loader_received(self, loader): loaders.ShortPlayerData.id): old_team = self.team team = self.protocol.teams[contained.team] + if self.on_team_join(team) == False: - if not team.spectator: - team = team.other + team = team.spectator + self.team = team if self.name is None: name = contained.name From e798d8e895a3678467e5a1fc495321694167566c Mon Sep 17 00:00:00 2001 From: matpow2 Date: Sat, 14 Jul 2012 02:53:24 +0200 Subject: [PATCH 10/36] Add 'team' parameter to /switch, fix bug with full/locked teams --- feature_server/commands.py | 12 +++++++++--- pyspades/server.py | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/feature_server/commands.py b/feature_server/commands.py index 906829ef..5a7efff1 100644 --- a/feature_server/commands.py +++ b/feature_server/commands.py @@ -90,6 +90,8 @@ def get_team(connection, value): return connection.protocol.blue_team elif value == 'green': return connection.protocol.green_team + elif value == 'spectator': + return connection.protocol.spectator_team raise InvalidTeam() def join_arguments(arg, default = None): @@ -301,7 +303,7 @@ def unlock(connection, value): team.name)) @admin -def switch(connection, player = None): +def switch(connection, player = None, team = None): protocol = connection.protocol if player is not None: player = get_player(protocol, player) @@ -312,9 +314,13 @@ def switch(connection, player = None): if player.team.spectator: player.send_chat("The switch command can't be used on a spectating player.") return + if team is None: + new_team = player.team.other + else: + new_team = get_team(connection, team) if player.invisible: old_team = player.team - player.team = player.team.other + player.team = new_team player.on_team_changed(old_team) player.spawn(player.world_object.position.get()) player.send_chat('Switched to %s team' % player.team.name) @@ -324,7 +330,7 @@ def switch(connection, player = None): protocol.irc_say('* %s silently switched teams' % player.name) else: player.respawn_time = protocol.respawn_time - player.set_team(player.team.other) + player.set_team(new_team) protocol.send_chat('%s switched teams' % player.name, irc = True) @name('setbalance') diff --git a/pyspades/server.py b/pyspades/server.py index 0fb956fe..83291d1c 100644 --- a/pyspades/server.py +++ b/pyspades/server.py @@ -237,7 +237,7 @@ def loader_received(self, loader): team = self.protocol.teams[contained.team] if self.on_team_join(team) == False: - team = team.spectator + team = self.protocol.spectator_team self.team = team if self.name is None: From 6d20ac409303cefd70746a069ad3ccec34b8547d Mon Sep 17 00:00:00 2001 From: duckslingers Date: Fri, 13 Jul 2012 22:45:30 -0300 Subject: [PATCH 11/36] Map editor's now calculates shadows for the VOXED preview so it looks the same as it will ingame. Several other changes --- pyspades/vxl.pxd | 4 ++- pyspades/vxl.pyx | 3 ++ pyspades/vxl_c.cpp | 31 ++++++++++++++++ pyspades/vxl_c.h | 18 +++++++++- tools/editor/run.py | 87 +++++++++++++++++++++++++++------------------ 5 files changed, 107 insertions(+), 36 deletions(-) diff --git a/pyspades/vxl.pxd b/pyspades/vxl.pxd index 09f6458e..4f79ad5b 100644 --- a/pyspades/vxl.pxd +++ b/pyspades/vxl.pxd @@ -26,6 +26,7 @@ cdef extern from "vxl_c.cpp": int get_random_point(int x1, int y1, int x2, int y2, MapData * map, float random_1, float random_2, int * x, int * y) bint is_valid_position(int x, int y, int z) + void update_shadows(MapData * map) cdef class VXLData: cdef MapData * map @@ -41,4 +42,5 @@ cdef class VXLData: cpdef bint check_node(self, int x, int y, int z, bint destroy = ?) cpdef bint build_point(self, int x, int y, int z, tuple color) cpdef bint set_column_fast(self, int x, int y, int start_z, - int end_z, int end_color_z, int color) \ No newline at end of file + int end_z, int end_color_z, int color) + cpdef update_shadows(self) \ No newline at end of file diff --git a/pyspades/vxl.pyx b/pyspades/vxl.pyx index 21103d03..97a17492 100644 --- a/pyspades/vxl.pyx +++ b/pyspades/vxl.pyx @@ -193,6 +193,9 @@ cdef class VXLData: set_column_color(x, y, z_start, z_color_end, self.map, color) return True + cpdef update_shadows(self): + update_shadows(self.map) + def get_overview(self, int z = -1, bint rgba = False): cdef unsigned int * data cdef unsigned int i, r, g, b, a, color diff --git a/pyspades/vxl_c.cpp b/pyspades/vxl_c.cpp index 0fbb8c74..1664352b 100644 --- a/pyspades/vxl_c.cpp +++ b/pyspades/vxl_c.cpp @@ -373,6 +373,37 @@ inline void get_random_point(int x1, int y1, int x2, int y2, MapData * map, } } +#define SHADOW_DISTANCE 18 +#define SHADOW_STEP 2 + +int sunblock(MapData * map, int x, int y, int z) +{ + int dec = SHADOW_DISTANCE; + int i = 127; + + while (dec && z) + { + if (get_solid_wrap(x, --y, --z, map)) + i -= dec; + dec -= SHADOW_STEP; + } + return i; +} + +void update_shadows(MapData * map) +{ + int x, y, z; + int a; + unsigned int color; + for (map_type::iterator iter = map->colors.begin(); + iter != map->colors.end(); ++iter) { + get_xyz(iter->first, &x, &y, &z); + color = iter->second; + a = sunblock(map, x, y, z); + iter->second = (color & 0x00FFFFFF) | (a << 24); + } +} + struct MapGenerator { MapData * map; diff --git a/pyspades/vxl_c.h b/pyspades/vxl_c.h index 97a46717..d4271ea9 100644 --- a/pyspades/vxl_c.h +++ b/pyspades/vxl_c.h @@ -11,7 +11,7 @@ #define MAP_X 512 #define MAP_Y 512 #define MAP_Z 64 -#define get_pos(x, y, z) (x + (y) * MAP_Y + (z) * MAP_X * MAP_Y) +#define get_pos(x, y, z) ((x) + (y) * MAP_Y + (z) * MAP_X * MAP_Y) #define DEFAULT_COLOR 0xFF674028 struct MapData @@ -21,6 +21,13 @@ struct MapData map_type colors; }; +void inline get_xyz(int pos, int* x, int* y, int* z) +{ + *x = pos % MAP_Y; + *y = (pos / MAP_Y) % MAP_X; + *z = pos / (MAP_X * MAP_Y); +} + int inline is_valid_position(int x, int y, int z) { return x >= 0 && x < 512 && y >= 0 && y < 512 && z >= 0 && z < 64; @@ -33,6 +40,15 @@ int inline get_solid(int x, int y, int z, MapData * map) return map->geometry[get_pos(x, y, z)]; } +int inline get_solid_wrap(int x, int y, int z, MapData * map) +{ + if (z < 0) + return 0; + else if (z >= 64) + return 1; + return map->geometry[get_pos(x & 511, y & 511, z)]; +} + int inline get_color(int x, int y, int z, MapData * map) { map_type::const_iterator iter = map->colors.find( diff --git a/tools/editor/run.py b/tools/editor/run.py index 79d78ac0..88ebd6b2 100644 --- a/tools/editor/run.py +++ b/tools/editor/run.py @@ -26,6 +26,7 @@ import math import subprocess +from collections import OrderedDict from PySide import QtGui, QtCore from PySide.QtCore import Qt from PySide.QtGui import QPainter @@ -352,10 +353,17 @@ def draw(self, painter, old_x, old_y, x, y): y - self.image.height() / 2.0, self.image) -TOOLS = { - 'Brush' : Brush, - 'Texture' : Texture -} +class Replace(Tool): + def draw(self, painter, old_x, old_y, x, y): + image = self.editor.image + color = struct.pack('I', self.editor.current_color.rgba()) + target_color = struct.pack('I', image.pixel(x, y)) + translate_color(image, color, target_color) + +TOOLS = OrderedDict() +TOOLS['Brush'] = Brush +TOOLS['Texture'] = Texture +TOOLS['Replace color'] = Replace class Settings(QtGui.QWidget): def __init__(self, parent, *arg, **kw): @@ -406,10 +414,8 @@ def freeze_image(self): self.editor.toggle_freeze() def set_color(self): - color = QtGui.QColorDialog.getColor( - Qt.white, self, 'Select a color', - QtGui.QColorDialog.ShowAlphaChannel - ) + color = QtGui.QColorDialog.getColor(self.editor.color, self, + 'Select a color', QtGui.QColorDialog.ShowAlphaChannel) self.editor.set_color(color) def update_values(self): @@ -501,8 +507,15 @@ def __init__(self, app, *arg, **kw): self.file.addSeparator() - self.voxed_action = QtGui.QAction('Open in &VOXED', - self, shortcut = QtGui.QKeySequence('F5'), triggered = self.open_voxed) + self.bake_voxed_action = QtGui.QAction( + '&Calculate shadows and open in VOXED', self, + shortcut = QtGui.QKeySequence('F5'), + triggered = self.bake_shadows_open_voxed) + self.file.addAction(self.bake_voxed_action) + + self.voxed_action = QtGui.QAction('Open in &VOXED', self, + shortcut = QtGui.QKeySequence('Shift+F5'), + triggered = self.open_voxed) self.file.addAction(self.voxed_action) self.file.addSeparator() @@ -674,12 +687,14 @@ def save_as_selected(self): self.save(name) return name - def save(self, filename): - for z in xrange(0, 64): - layer = self.layers[z] + def update_map(self): + for z, layer in enumerate(self.layers): if layer.dirty: self.map.set_overview(layer.data, z) layer.dirty = False + + def save(self, filename): + self.update_map() open(filename, 'wb').write(self.map.generate()) self.set_dirty(False) @@ -699,9 +714,15 @@ def open_voxed(self): self.voxed_filename = exename subprocess.call([self.voxed_filename, name]) + def bake_shadows_open_voxed(self): + self.update_map() + self.map.update_shadows() + self.open_voxed() + def import_image_sequence(self): name = QtGui.QFileDialog.getOpenFileName(self, - 'Import image sequence (select any file from the sequence)', filter = IMAGE_OPEN_FILTER)[0] + 'Import image sequence (select any file from the sequence)', + filter = IMAGE_OPEN_FILTER)[0] if not name: return root, ext = os.path.splitext(name) @@ -709,7 +730,7 @@ def import_image_sequence(self): path = os.path.join(root, head, tail[:-2]) old_z = self.edit_widget.z progress = progress_dialog(self.edit_widget, 0, 63, 'Importing images...') - for z in xrange(0, 64): + for z, layer in enumerate(reversed(self.layers)): if progress.wasCanceled(): break progress.setValue(z) @@ -717,7 +738,7 @@ def import_image_sequence(self): if not image: continue interpret_colorkey(image) - self.layers[63 - z].set_image(image) + layer.set_image(image) self.edit_widget.repaint() self.set_dirty() @@ -735,11 +756,10 @@ def export_color_map(self): for x in xrange(0, 512): height_found[y].append(False) progress = progress_dialog(self.edit_widget, 0, 63, 'Exporting Colormap...') - for z in xrange(0, 64): + for z, image in enumerate(self.layers): if progress.wasCanceled(): break progress.setValue(z) - image = self.layers[z] for y in xrange(0, 512): image_line = image.scanLine(y) color_line = color_lines[y] @@ -760,7 +780,8 @@ def export_height_map(self): height_packed = [] for z in xrange(0, 64): height = (63 - z) * 4 - height_packed.append(struct.pack('I', QtGui.qRgba(height, height, height, 255))) + height_packed.append(struct.pack('I', + QtGui.qRgba(height, height, height, 255))) height_image = QImage(512, 512, QImage.Format_ARGB32) height_lines = [] height_found = [] @@ -770,12 +791,11 @@ def export_height_map(self): for x in xrange(0, 512): height_found[y].append(False) progress = progress_dialog(self.edit_widget, 0, 63, 'Exporting Heightmap...') - for z in xrange(0, 64): + for z, image in enumerate(self.layers): if progress.wasCanceled(): break progress.setValue(z) packed_value = height_packed[z] - image = self.layers[z] for y in xrange(0, 512): image_line = image.scanLine(y) height_line = height_lines[y] @@ -789,16 +809,16 @@ def export_height_map(self): def export_image_sequence(self): name = QtGui.QFileDialog.getSaveFileName(self, - 'Export image sequence (select base filename)', filter = IMAGE_SAVE_FILTER)[0] + 'Export image sequence (select base filename)', + filter = IMAGE_SAVE_FILTER)[0] if not name: return root, ext = os.path.splitext(name) progress = progress_dialog(self.edit_widget, 0, 63, 'Exporting images...') - for z in xrange(0, 64): + for z, image in enumerate(reversed(self.layers)): if progress.wasCanceled(): break progress.setValue(z) - image = self.layers[63 - z] new_image = make_colorkey(image) new_image.save(root + format(z, '02d') + ext) @@ -828,11 +848,10 @@ def paste_external_selected(self): def mirror(self, hor, ver): progress = progress_dialog(self.edit_widget, 0, 63, 'Mirroring...') - for z in xrange(0, 64): + for z, layer in enumerate(self.layers): if progress.wasCanceled(): break progress.setValue(z) - layer = self.layers[z] layer.set_image(layer.mirrored(hor, ver)) self.edit_widget.repaint() self.set_dirty() @@ -857,11 +876,10 @@ def rotate(self, angle): progress = progress_dialog(self.edit_widget, 0, 63, 'Rotating...') transform = QtGui.QTransform() transform.rotate(angle) - for z in xrange(0, 64): + for z, layer in enumerate(self.layers): if progress.wasCanceled(): break progress.setValue(z) - layer = self.layers[z] layer.set_image(layer.transformed(transform)) self.edit_widget.repaint() self.set_dirty() @@ -893,8 +911,8 @@ def clear_selected(self): self.set_dirty() def get_height(self, color): - return int(math.floor((float(QtGui.qRed(color)) + float(QtGui.qGreen(color)) + - float(QtGui.qBlue(color)))/12.0)) + return int(math.floor((float(QtGui.qRed(color)) + + float(QtGui.qGreen(color)) + float(QtGui.qBlue(color))) / 12.0)) def generate_heightmap(self, delete = False): h_name = QtGui.QFileDialog.getOpenFileName(self, @@ -916,15 +934,16 @@ def generate_heightmap(self, delete = False): if not delete: color_lines.append(c_image.scanLine(y)) for x in xrange(0,512): - height_values[y].append(self.get_height(struct.unpack('I', height_line[x * 4:x * 4 + 4])[0])) - progress = progress_dialog(self.edit_widget, 0, 63, 'Generating from heightmap...') - for z in xrange(0, 64): + height_values[y].append(self.get_height( + struct.unpack('I', height_line[x * 4:x * 4 + 4])[0])) + progress = progress_dialog(self.edit_widget, 0, 63, + 'Generating from heightmap...') + for z, image in enumerate(reversed(self.layers)): if progress.wasCanceled(): break progress.setValue(z) if z == 0 and delete: continue - image = self.layers[63 - z] for y in xrange(0, 512): image_line = image.scanLine(y) if not delete: From 30a07d756e32942a23e49444565257c62c77d565 Mon Sep 17 00:00:00 2001 From: Yourself Date: Sat, 14 Jul 2012 12:09:26 -0500 Subject: [PATCH 12/36] Fixed issue 257 --- contrib/scripts/arena.py | 7 +++---- feature_server/scripts/squad.py | 6 ++++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/contrib/scripts/arena.py b/contrib/scripts/arena.py index 13a803ed..94e300e4 100644 --- a/contrib/scripts/arena.py +++ b/contrib/scripts/arena.py @@ -371,11 +371,10 @@ def arena_time_limit(self): def arena_win(self, team, killer = None): if not self.arena_running: return - if self.arena_old_fog_color is None: + if self.arena_old_fog_color is None and TEAM_COLOR_TIME > 0: self.arena_old_fog_color = self.fog_color - if TEAM_COLOR_TIME > 0: - self.set_fog_color(team.color) - reactor.callLater(TEAM_COLOR_TIME, self.arena_reset_fog_color) + self.set_fog_color(team.color) + reactor.callLater(TEAM_COLOR_TIME, self.arena_reset_fog_color) if killer is None or killer.team is not team: for player in team.get_players(): if not player.world_object.dead: diff --git a/feature_server/scripts/squad.py b/feature_server/scripts/squad.py index 50bea5b2..c65b3e60 100644 --- a/feature_server/scripts/squad.py +++ b/feature_server/scripts/squad.py @@ -221,7 +221,8 @@ def on_spawn(self, pos): all_members = ([n for n in self.get_squad(self.team, self.squad)['players'] if n is not self]) - live_members = [n for n in all_members if n.hp] + live_members = [n for n in all_members if n.hp and not n.invisible and + not n.god] membernames = [m.name for m in all_members] memberstr = "" for n in xrange(len(all_members)): @@ -243,7 +244,8 @@ def on_spawn(self, pos): self.send_chat('You are in squad %s, all alone.' % self.squad) if (self.squad_pref is not None and self.squad_pref.hp and - self.squad_pref.team is self.team): + self.squad_pref.team is self.team and not self.squad_pref.invisible + and not self.squad_pref.god): self.set_location_safe(self.get_follow_location( self.squad_pref)) else: From 7eff2d3921832ad4928c01cc28b8c33a7bc355b3 Mon Sep 17 00:00:00 2001 From: matpow2 Date: Tue, 17 Jul 2012 21:41:45 +0200 Subject: [PATCH 13/36] Add /kill suicide and change on_team_join API (so you can return a team if you want to) --- feature_server/commands.py | 60 ++++++++++++++++++++++---------------- feature_server/irc.py | 2 +- pyspades/server.py | 9 ++++-- 3 files changed, 43 insertions(+), 28 deletions(-) diff --git a/feature_server/commands.py b/feature_server/commands.py index 5a7efff1..0d5ed97f 100644 --- a/feature_server/commands.py +++ b/feature_server/commands.py @@ -21,9 +21,12 @@ from pyspades.common import prettify_timespan from pyspades.server import parse_command from twisted.internet import reactor - from map import check_rotation +commands = {} +aliases = {} +rights = {} + class InvalidPlayer(Exception): pass @@ -33,15 +36,27 @@ class InvalidSpectator(InvalidPlayer): class InvalidTeam(Exception): pass -def restrict(func, user_types): +def add_rights(func_name, *user_types): + for user_type in user_types: + if user_type in rights: + rights[user_type].add(func_name) + else: + rights[user_type] = set([func_name]) + +def restrict(func, *user_types): def new_func(connection, *arg, **kw): return func(connection, *arg, **kw) new_func.func_name = func.func_name - new_func.user_types = set(user_types) + new_func.is_restricted = True + add_rights(func.func_name, *user_types) return new_func +def has_rights(f, connection): + is_restricted = getattr(f, 'is_restricted', False) + return not is_restricted or f.func_name in connection.rights + def admin(func): - return restrict(func, ('admin',)) + return restrict(func, 'admin') def name(name): def dec(func): @@ -196,11 +211,19 @@ def say(connection, *arg): connection.protocol.send_chat(value) connection.protocol.irc_say(value) -@admin -def kill(connection, value): - player = get_player(connection.protocol, value, False) +add_rights('kill', 'admin') +def kill(connection, value = None): + if value is None: + player = connection + else: + if not connection.rights.kill: + return "You can't use this command" + player = get_player(connection.protocol, value, False) player.kill() - message = '%s killed %s' % (connection.name, player.name) + if connection is player: + message = '%s committed suicide!' % connection.name + else: + message = '%s killed %s' % (connection.name, player.name) connection.protocol.send_chat(message, irc = True) @admin @@ -907,10 +930,6 @@ def weapon(connection, value): mapname ] -commands = {} -aliases = {} -rights = {} - def add(func, name = None): """ Function to add a command from scripts @@ -918,13 +937,6 @@ def add(func, name = None): if name is None: name = func.func_name name = name.lower() - user_types = getattr(func, 'user_types', None) - if user_types is not None: - for user_type in user_types: - if user_type in rights: - rights[user_type].add(name) - else: - rights[user_type] = set([name]) commands[name] = func try: for alias in func.aliases: @@ -981,9 +993,8 @@ def handle_command(connection, command, parameters): except KeyError: return # 'Invalid command' try: - if (hasattr(command_func, 'user_types') and - command_func.func_name not in connection.rights): - return "You can't use this command" + if not has_rights(command_func, connection): + return "You can't use this command" return command_func(connection, *parameters) except KeyError: return # 'Invalid command' @@ -1009,9 +1020,8 @@ def debug_handle_command(connection, command, parameters): command_func = commands[command] except KeyError: return # 'Invalid command' - if (hasattr(command_func, 'user_types') and - command_func.func_name not in connection.rights): - return "You can't use this command" + if not has_rights(command_func, connection): + return "You can't use this command" return command_func(connection, *parameters) # handle_command = debug_handle_command diff --git a/feature_server/irc.py b/feature_server/irc.py index e3dc217f..85ea5d7f 100644 --- a/feature_server/irc.py +++ b/feature_server/irc.py @@ -237,7 +237,7 @@ def format_name_color(player): '%s #%s' % (player.name, player.player_id)) def irc(func): - return commands.restrict(func, ('irc',)) + return commands.restrict(func, 'irc') @irc def who(connection): diff --git a/pyspades/server.py b/pyspades/server.py index 83291d1c..fdb5dc76 100644 --- a/pyspades/server.py +++ b/pyspades/server.py @@ -236,8 +236,11 @@ def loader_received(self, loader): old_team = self.team team = self.protocol.teams[contained.team] - if self.on_team_join(team) == False: + ret = self.on_team_join(team) + if ret is False: team = self.protocol.spectator_team + elif ret is not None: + team = ret self.team = team if self.name is None: @@ -603,8 +606,10 @@ def loader_received(self, loader): self.set_weapon(self.weapon) elif contained.id == loaders.ChangeTeam.id: team = self.protocol.teams[contained.team] - if self.on_team_join(team) == False: + ret = self.on_team_join(team) + if ret is False: return + team = ret or team self.set_team(team) def is_valid_position(self, x, y, z, distance = None): From a15577f6da97b9e2a566c8e4c16e025620de5fe1 Mon Sep 17 00:00:00 2001 From: matpow2 Date: Thu, 19 Jul 2012 01:43:32 +0200 Subject: [PATCH 14/36] Fix some full admin commands being broken --- feature_server/commands.py | 13 +++++-------- feature_server/run.py | 5 ++++- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/feature_server/commands.py b/feature_server/commands.py index 0d5ed97f..5cb01e73 100644 --- a/feature_server/commands.py +++ b/feature_server/commands.py @@ -47,13 +47,11 @@ def restrict(func, *user_types): def new_func(connection, *arg, **kw): return func(connection, *arg, **kw) new_func.func_name = func.func_name - new_func.is_restricted = True - add_rights(func.func_name, *user_types) + new_func.user_types = user_types return new_func def has_rights(f, connection): - is_restricted = getattr(f, 'is_restricted', False) - return not is_restricted or f.func_name in connection.rights + return not hasattr(f, 'user_types') or f.func_name in connection.rights def admin(func): return restrict(func, 'admin') @@ -220,11 +218,9 @@ def kill(connection, value = None): return "You can't use this command" player = get_player(connection.protocol, value, False) player.kill() - if connection is player: - message = '%s committed suicide!' % connection.name - else: + if connection is not player: message = '%s killed %s' % (connection.name, player.name) - connection.protocol.send_chat(message, irc = True) + connection.protocol.send_chat(message, irc = True) @admin def heal(connection, player = None): @@ -937,6 +933,7 @@ def add(func, name = None): if name is None: name = func.func_name name = name.lower() + add_rights(name, *getattr(func, 'user_types', ())) commands[name] = func try: for alias in func.aliases: diff --git a/feature_server/run.py b/feature_server/run.py index 8c1976dd..e915a04d 100644 --- a/feature_server/run.py +++ b/feature_server/run.py @@ -668,7 +668,10 @@ def __init__(self, interface, config): print 'REMEMBER TO CHANGE THE DEFAULT ADMINISTRATOR PASSWORD!' elif not password: self.everyone_is_admin = True - commands.rights.update(config.get('rights', {})) + + for user_type, func_names in config.get('rights', {}).iteritems(): + for func_name in func_names: + commands.add_rights(func_name, user_type) port = self.port = config.get('port', 32887) ServerProtocol.__init__(self, port, interface) From 103cb3834a5204ed08b748171f38961f60bd26fc Mon Sep 17 00:00:00 2001 From: matpow2 Date: Sun, 22 Jul 2012 03:18:45 +0200 Subject: [PATCH 15/36] It turns out that VOXLAP's ftol uses fistp which doesn't simply discard the fraction part, but rounds to the nearest integer --- pyspades/server.py | 6 +++--- pyspades/world_c.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyspades/server.py b/pyspades/server.py index fdb5dc76..ab707b49 100644 --- a/pyspades/server.py +++ b/pyspades/server.py @@ -1013,9 +1013,9 @@ def grenade_exploded(self, grenade): z = position.z if x < 0 or x > 512 or y < 0 or y > 512 or z < 0 or z > 63: return - x = int(x) - y = int(y) - z = int(z) + x = int(math.floor(x)) + y = int(math.floor(y)) + z = int(math.floor(z)) for player_list in (self.team.other.get_players(), (self,)): for player in player_list: if not player.hp: diff --git a/pyspades/world_c.cpp b/pyspades/world_c.cpp index 6270c4ba..4b228791 100644 --- a/pyspades/world_c.cpp +++ b/pyspades/world_c.cpp @@ -125,7 +125,7 @@ int validate_hit(float shooter_x, float shooter_y, float shooter_z, // silly VOXLAP function inline void ftol(float f, long *a) { - *a = (long)f; + *a = (long)floor(f+0.5f); } //same as isvoxelsolid but water is empty && out of bounds returns true From 90702385b199d049019ed0db6012d320b571c9ac Mon Sep 17 00:00:00 2001 From: matpow2 Date: Thu, 2 Aug 2012 15:11:52 +0200 Subject: [PATCH 16/36] Change default autobalancing behaviour to move player to the other team (and not to spectator unless the team is locked) --- feature_server/run.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/feature_server/run.py b/feature_server/run.py index e915a04d..c0acd2f6 100644 --- a/feature_server/run.py +++ b/feature_server/run.py @@ -369,13 +369,17 @@ def on_team_join(self, team): return False if team.locked: self.send_chat('Team is locked') + if not team.spectator and not team.other.locked: + return team.other return False balanced_teams = self.protocol.balanced_teams if balanced_teams and not team.spectator: other_team = team.other if other_team.count() < team.count() + 1 - balanced_teams: - self.send_chat('Team is full') - return False + if other_team.locked: + return False + self.send_chat('Team is full, moved to %s' % other_team.name) + return other_team self.last_switch = reactor.seconds() def on_chat(self, value, global_message): From 63cce204b5dd360b996be4e3a9c7b6a6bc676962 Mon Sep 17 00:00:00 2001 From: matpow2 Date: Thu, 2 Aug 2012 15:14:31 +0200 Subject: [PATCH 17/36] Fix issue 277 --- pyspades/vxl_c.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyspades/vxl_c.cpp b/pyspades/vxl_c.cpp index 1664352b..20641a59 100644 --- a/pyspades/vxl_c.cpp +++ b/pyspades/vxl_c.cpp @@ -189,8 +189,8 @@ int check_node(int x, int y, int z, MapData * map, int destroy) inline int is_surface(MapData * map, int x, int y, int z) { - if (z == 0) return 1; if (map->geometry[get_pos(x, y, z)]==0) return 0; + if (z == 0) return 1; if (x > 0 && map->geometry[get_pos(x-1, y, z)]==0) return 1; if (x+1 < 512 && map->geometry[get_pos(x+1, y, z)]==0) return 1; if (y > 0 && map->geometry[get_pos(x, y-1, z)]==0) return 1; From 552fcc9bc7353fd068a33bf1d2104261e89068fd Mon Sep 17 00:00:00 2001 From: duckslingers Date: Wed, 15 Aug 2012 22:27:20 -0300 Subject: [PATCH 18/36] Map editor: minor changes --- tools/editor/run.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tools/editor/run.py b/tools/editor/run.py index 88ebd6b2..8e474f99 100644 --- a/tools/editor/run.py +++ b/tools/editor/run.py @@ -24,6 +24,7 @@ import os sys.path.append('../../') +import re import math import subprocess from collections import OrderedDict @@ -814,13 +815,15 @@ def export_image_sequence(self): if not name: return root, ext = os.path.splitext(name) + head, tail = os.path.split(root) + path = os.path.join(root, head, re.sub('\d{2}$', '', tail)) progress = progress_dialog(self.edit_widget, 0, 63, 'Exporting images...') for z, image in enumerate(reversed(self.layers)): if progress.wasCanceled(): break progress.setValue(z) new_image = make_colorkey(image) - new_image.save(root + format(z, '02d') + ext) + new_image.save(path + format(z, '02d') + ext) def copy_selected(self): self.clipboard.setImage(self.edit_widget.image) @@ -968,10 +971,9 @@ def quit(self): reply = QMessageBox.warning(self, self.app.applicationName(), text, QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel, QMessageBox.Yes) - if reply == QMessageBox.Yes: - if not self.save_selected(): - return - elif reply == QMessageBox.Cancel: + if reply == QMessageBox.Cancel: + return + elif reply == QMessageBox.Yes and not self.save_selected(): return self.app.exit() From 487fcd9945852ba52e66d904287b9f5839ecf3ec Mon Sep 17 00:00:00 2001 From: duckslingers Date: Wed, 15 Aug 2012 22:28:54 -0300 Subject: [PATCH 19/36] Moved some stuff around in grownade.py --- feature_server/scripts/grownade.py | 68 +++++++++++++++++------------- 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/feature_server/scripts/grownade.py b/feature_server/scripts/grownade.py index 2b20d496..c5ee3b0d 100644 --- a/feature_server/scripts/grownade.py +++ b/feature_server/scripts/grownade.py @@ -68,6 +68,31 @@ S_SPECIFY_FILES = 'Specify model files to load. Wildcards are allowed, ' \ 'e.g.: "bunker", "tree*"' +class ModelLoadFailure(Exception): + pass + +def load_models(expression): + if not os.path.isdir(KV6_DIR): + raise ModelLoadFailure(S_NO_KV6_FOLDER) + if not os.path.splitext(expression)[-1]: + # append default extension + expression += '.kv6' + paths = glob(os.path.join(KV6_DIR, expression)) + if not paths: + raise ModelLoadFailure(S_NO_MATCHES.format(expression = expression)) + + # attempt to load models, discard invalid ones + models, loaded, failed = [], [], [] + for path in paths: + model = KV6Model(path) + filename = os.path.split(path)[-1] + if model.voxels: + models.append(model) + loaded.append(filename) + else: + failed.append(filename) + return models, loaded, failed + @name('model') @admin def model_grenades(connection, expression = None): @@ -78,35 +103,20 @@ def model_grenades(connection, expression = None): result = None if expression: - if not os.path.isdir(KV6_DIR): - return S_NO_KV6_FOLDER - if not os.path.splitext(expression)[-1]: - # append default extension - expression += '.kv6' - paths = glob(os.path.join(KV6_DIR, expression)) - if not paths: - return S_NO_MATCHES.format(expression = expression) - - # attempt to load models, discard invalid ones - models, loaded, failed = [], [], [] - for path in paths: - model = KV6Model(path) - filename = os.path.split(path)[-1] - if model.voxels: - models.append(model) - loaded.append(filename) - else: - failed.append(filename) - if len(loaded) == 1: - result = S_LOADED_SINGLE.format(filename = loaded[0]) - elif len(loaded) > 1: - result = S_LOADED.format(filenames = ', '.join(loaded)) - if failed: - player.send_chat(S_FAILED.format(filenames = ', '.join(failed))) - player.send_chat(S_PIVOT_TIP) - - if models: - player.grenade_models = models + try: + models, loaded, failed = load_models(expression) + except ModelLoadFailure as err: + result = str(err) + else: + if len(loaded) == 1: + result = S_LOADED_SINGLE.format(filename = loaded[0]) + elif len(loaded) > 1: + result = S_LOADED.format(filenames = ', '.join(loaded)) + if failed: + player.send_chat(S_FAILED.format(filenames = ', '.join(failed))) + player.send_chat(S_PIVOT_TIP) + if models: + player.grenade_models = models elif player.grenade_models: player.grenade_models = None result = S_CANCEL From cb0d5ba1b7c460c36dbf7ec2c775c2017f3daf09 Mon Sep 17 00:00:00 2001 From: duckslingers Date: Wed, 15 Aug 2012 23:25:48 -0300 Subject: [PATCH 20/36] Changed platform timing --- feature_server/scripts/platform.py | 50 +++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/feature_server/scripts/platform.py b/feature_server/scripts/platform.py index 57c69324..816206d3 100644 --- a/feature_server/scripts/platform.py +++ b/feature_server/scripts/platform.py @@ -161,6 +161,12 @@ Saves all platform and button data to mapname_platform.txt Use SAVE_ON_MAP_CHANGE and AUTOSAVE_EVERY to avoid having to manually save. +/reach + Increases your button activation reach. Meant to be used for getting rid + of distance triggering buttons from a safe distance. + + Use again to revert to normal. + Maintainer: hompy """ @@ -198,6 +204,8 @@ MIN_COOLDOWN = 0.1 # seconds S_SAVED = 'Platforms saved' +S_REACH = 'You can now reach to buttons from from far away' +S_NO_REACH = 'Button reach reverted to normal' S_EXIT_BLOCKING_STATE = "You must first leave {state} mode!" S_NOT_WORKING = 'This button is disabled' S_COMMAND_CANCEL = "Aborting {command} command" @@ -326,6 +334,7 @@ } ACTION_RAY_LENGTH = 8.0 +ACTION_RAY_LENGTH_LONG = 32.0 ACTION_COOLDOWN = 0.25 def parseargs(signature, args): @@ -356,6 +365,12 @@ def save(connection): connection.protocol.dump_platform_json() return S_SAVED +@admin +def reach(connection): + long = connection.reach == ACTION_RAY_LENGTH_LONG + connection.reach = ACTION_RAY_LENGTH if long else ACTION_RAY_LENGTH_LONG + return S_REACH if not long else S_NO_REACH + @alias('p') @name('platform') @admin @@ -692,7 +707,7 @@ def trigger_command(connection, *args): return usage for func in (platform_command, button_command, action_command, - trigger_command, save): + trigger_command, save, reach): add(func) def aabb(x, y, z, x1, y1, z1, x2, y2, z2): @@ -1097,6 +1112,7 @@ class Platform(BaseObject): frozen = False mode = None busy = False + running = False delay_call = None bound_triggers = None @@ -1107,7 +1123,6 @@ def __init__(self, protocol, id, x1, y1, z1, x2, y2, z2, color): self.z, self.start_z = z1, z2 self.height = self.start_z - self.z self.color = color - self.cycle_loop = LoopingCall(self.cycle) for x, y, z in prism(x1, y1, z1, x2, y2, z2): protocol.map.set_point(x, y, z, color) @@ -1135,9 +1150,6 @@ def release(self): if self.delay_call and self.delay_call.active(): self.delay_call.cancel() self.delay_call = None - if self.cycle_loop and self.cycle_loop.running: - self.cycle_loop.stop() - self.cycle_loop = None def start(self, height, mode, speed, delay, wait = None, force = False): if self.busy and not force: @@ -1154,19 +1166,22 @@ def start(self, height, mode, speed, delay, wait = None, force = False): if self.z == self.target_z: return self.busy = True + self.ticks_per_cycle = int(speed / UPDATE_FREQUENCY) + self.ticks_left = self.ticks_per_cycle self.start_cycle_later(delay) def start_cycle_later(self, delay): + self.running = False if self.delay_call and self.delay_call.active(): self.delay_call.cancel() self.delay_call = None - if self.cycle_loop and self.cycle_loop.running: - self.cycle_loop.stop() - self.cycle_loop = LoopingCall(self.cycle) if delay > 0.0: - self.delay_call = callLater(delay, self.cycle_loop.start, self.speed) + self.delay_call = callLater(delay, self.run) else: - self.cycle_loop.start(self.speed) + self.run() + + def run(self): + self.running = True def cycle(self): if self.frozen: @@ -1178,6 +1193,8 @@ def cycle(self): # unstuck players for player in self.protocol.players.itervalues(): obj = player.world_object + if obj is None: + continue looking_up = obj.orientation.get()[2] < 0.4 # 0.5 (allow lag) x, y, z = obj.position.get() if aabb(x, y, z, self.x1, self.y1, self.z - 2, @@ -1199,14 +1216,12 @@ def cycle(self): self.z += 1 self.height = self.start_z - self.z if self.z == self.target_z: - self.cycle_loop.stop() - self.cycle_loop = None if self.mode == 'elevator': self.mode = 'return' self.target_z = self.last_z self.start_cycle_later(self.wait) else: - self.busy = False + self.busy = self.running = False if self.bound_triggers: for trigger in self.bound_triggers: trigger.callback(self) @@ -1267,7 +1282,7 @@ def player_action(player, select, inspect): if last_action is not None and seconds() - last_action <= ACTION_COOLDOWN: return player.last_action = seconds() - location = player.world_object.cast_ray(ACTION_RAY_LENGTH) + location = player.world_object.cast_ray(player.reach) if location is None: return x, y, z = location @@ -1696,6 +1711,7 @@ class PlatformConnection(connection): last_action = None previous_button = None previous_platform = None + reach = ACTION_RAY_LENGTH def on_reset(self): if self.states: @@ -1823,6 +1839,12 @@ def on_world_update(self): for player in self.players.itervalues(): for trigger in self.position_triggers: trigger.callback(player) + for platform in self.platforms.itervalues(): + if platform.running: + platform.ticks_left -= 1 + if platform.ticks_left <= 0: + platform.ticks_left = platform.ticks_per_cycle + platform.cycle() protocol.on_world_update(self) def get_platform_json_path(self): From e8a738aa145d48b46eb1afe4486e7265657da7d9 Mon Sep 17 00:00:00 2001 From: duckslingers Date: Thu, 16 Aug 2012 18:15:08 -0300 Subject: [PATCH 21/36] Minor platform sanity check --- feature_server/scripts/platform.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/feature_server/scripts/platform.py b/feature_server/scripts/platform.py index 816206d3..4fb28104 100644 --- a/feature_server/scripts/platform.py +++ b/feature_server/scripts/platform.py @@ -177,7 +177,6 @@ # Prevent platforms from being hidden forever # Negative heights # Nicer way of having invisible buttons? -# 'invisibility' mode to get in range of annoying distance buttons # Platforms crushing players # Stop platform action? @@ -367,6 +366,8 @@ def save(connection): @admin def reach(connection): + if connection not in connection.protocol.players: + raise ValueError() long = connection.reach == ACTION_RAY_LENGTH_LONG connection.reach = ACTION_RAY_LENGTH if long else ACTION_RAY_LENGTH_LONG return S_REACH if not long else S_NO_REACH From 0e8c707ab27a30a92c0620dc606a86e45679d327 Mon Sep 17 00:00:00 2001 From: duckslingers Date: Sat, 25 Aug 2012 15:19:02 -0300 Subject: [PATCH 22/36] Platform changes --- feature_server/scripts/platform.py | 42 ++++++++++++++++++------------ 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/feature_server/scripts/platform.py b/feature_server/scripts/platform.py index 4fb28104..ae3392b6 100644 --- a/feature_server/scripts/platform.py +++ b/feature_server/scripts/platform.py @@ -64,10 +64,10 @@ See /trigger for more information on who the "activating players" are. action: - height [speed=0.25] [delay] - raise [speed=0.25] [delay] - lower [speed=0.25] [delay] - elevator [speed=0.75] [delay] [wait=3.0] + height [speed=0.15] [delay] + raise [speed=0.15] [delay] + lower [speed=0.15] [delay] + elevator [speed=0.25] [delay] [wait=3.0] Speed determines how fast the platform moves, in seconds. Delay is the amount of time spent waiting before the platform actually starts to move. @@ -197,7 +197,7 @@ from commands import add, admin, name, alias, join_arguments from map import DEFAULT_LOAD_DIR -SAVE_ON_MAP_CHANGE = True +SAVE_ON_MAP_CHANGE = False AUTOSAVE_EVERY = 0.0 # minutes, 0 = disabled MAX_DISTANCE = 64.0 MIN_COOLDOWN = 0.1 # seconds @@ -246,10 +246,10 @@ S_ACTION_USAGE = 'Usage: /action <{commands}>' S_ACTION_ADD_USAGE = 'Usage: /action add <{actions}>' S_ACTION_DELETE_USAGE = 'Usage: /action del <#|all>' -S_ACTION_HEIGHT_USAGE = 'Usage: /action add height [speed=0.25] [delay]' -S_ACTION_RAISE_USAGE = 'Usage: /action add raise [speed=0.25] [delay]' -S_ACTION_LOWER_USAGE = 'Usage: /action add lower [speed=0.25] [delay]' -S_ACTION_ELEVATOR_USAGE = 'Usage: /action add elevator [speed=0.75] ' \ +S_ACTION_HEIGHT_USAGE = 'Usage: /action add height [speed=0.15] [delay]' +S_ACTION_RAISE_USAGE = 'Usage: /action add raise [speed=0.15] [delay]' +S_ACTION_LOWER_USAGE = 'Usage: /action add lower [speed=0.15] [delay]' +S_ACTION_ELEVATOR_USAGE = 'Usage: /action add elevator [speed=0.25] ' \ '[delay] [wait=3.0]' S_ACTION_OUTPUT_USAGE = 'Usage: /action add output [delay]' S_ACTION_TELEPORT_USAGE = 'Usage: /action add teleport ' @@ -548,12 +548,12 @@ def action_command(connection, *args): if action == 'elevator': signature = 'int [float float float]' value, speed, delay, wait = parseargs(signature, args[2:]) - speed = 0.75 if speed is None else speed + speed = 0.25 if speed is None else speed kwargs['wait'] = 3.0 if wait is None else wait else: signature = 'int [float float]' value, speed, delay = parseargs(signature, args[2:]) - speed = 0.25 if speed is None else speed + speed = 0.15 if speed is None else speed kwargs['mode'] = action kwargs['height'] = value kwargs['speed'] = speed @@ -1167,6 +1167,7 @@ def start(self, height, mode, speed, delay, wait = None, force = False): if self.z == self.target_z: return self.busy = True + self.protocol.running_platforms.add(self) self.ticks_per_cycle = int(speed / UPDATE_FREQUENCY) self.ticks_left = self.ticks_per_cycle self.start_cycle_later(delay) @@ -1183,6 +1184,7 @@ def start_cycle_later(self, delay): def run(self): self.running = True + self.protocol.running_platforms.add(self) def cycle(self): if self.frozen: @@ -1805,6 +1807,7 @@ class PlatformProtocol(protocol): highest_id = None platforms = None platform_json_dirty = False + running_platforms = None buttons = None position_triggers = None autosave_loop = None @@ -1812,6 +1815,7 @@ class PlatformProtocol(protocol): def on_map_change(self, map): self.highest_id = -1 self.platforms = {} + self.running_platforms = set() self.buttons = MultikeyDict() self.position_triggers = [] self.platform_json_dirty = False @@ -1832,6 +1836,7 @@ def on_map_leave(self): for button in self.buttons.itervalues(): button.release() self.platforms = None + self.running_platforms = None self.buttons = None self.position_triggers = None protocol.on_map_leave(self) @@ -1840,12 +1845,15 @@ def on_world_update(self): for player in self.players.itervalues(): for trigger in self.position_triggers: trigger.callback(player) - for platform in self.platforms.itervalues(): - if platform.running: - platform.ticks_left -= 1 - if platform.ticks_left <= 0: - platform.ticks_left = platform.ticks_per_cycle - platform.cycle() + not_running = set() + for platform in self.running_platforms: + platform.ticks_left -= 1 + if platform.ticks_left <= 0: + platform.ticks_left = platform.ticks_per_cycle + platform.cycle() + if not platform.running: + not_running.add(platform) + self.running_platforms -= not_running protocol.on_world_update(self) def get_platform_json_path(self): From c8be87310ba59c8fde0174763dafdc84388b24ec Mon Sep 17 00:00:00 2001 From: duckslingers Date: Sat, 25 Aug 2012 16:37:32 -0300 Subject: [PATCH 23/36] Fix exception --- feature_server/scripts/platform.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/feature_server/scripts/platform.py b/feature_server/scripts/platform.py index ae3392b6..0fef5512 100644 --- a/feature_server/scripts/platform.py +++ b/feature_server/scripts/platform.py @@ -1225,6 +1225,7 @@ def cycle(self): self.start_cycle_later(self.wait) else: self.busy = self.running = False + self.protocol.running_platforms.discard(self) if self.bound_triggers: for trigger in self.bound_triggers: trigger.callback(self) @@ -1845,15 +1846,11 @@ def on_world_update(self): for player in self.players.itervalues(): for trigger in self.position_triggers: trigger.callback(player) - not_running = set() - for platform in self.running_platforms: + for platform in list(self.running_platforms): platform.ticks_left -= 1 if platform.ticks_left <= 0: platform.ticks_left = platform.ticks_per_cycle platform.cycle() - if not platform.running: - not_running.add(platform) - self.running_platforms -= not_running protocol.on_world_update(self) def get_platform_json_path(self): From bb7ddea7a4b6600ed8abfca17a6d0db2ca85f4a1 Mon Sep 17 00:00:00 2001 From: duckslingers Date: Sat, 25 Aug 2012 16:38:34 -0300 Subject: [PATCH 24/36] Typo --- feature_server/scripts/platform.py | 1 - 1 file changed, 1 deletion(-) diff --git a/feature_server/scripts/platform.py b/feature_server/scripts/platform.py index 0fef5512..2f85ad29 100644 --- a/feature_server/scripts/platform.py +++ b/feature_server/scripts/platform.py @@ -1167,7 +1167,6 @@ def start(self, height, mode, speed, delay, wait = None, force = False): if self.z == self.target_z: return self.busy = True - self.protocol.running_platforms.add(self) self.ticks_per_cycle = int(speed / UPDATE_FREQUENCY) self.ticks_left = self.ticks_per_cycle self.start_cycle_later(delay) From 9ede53f82d52b89416841f48c02b729fd0249895 Mon Sep 17 00:00:00 2001 From: matpow2 Date: Mon, 27 Aug 2012 22:25:14 +0200 Subject: [PATCH 25/36] Fix issue 282 --- feature_server/map.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/feature_server/map.py b/feature_server/map.py index 33adb9d3..cd6a7956 100644 --- a/feature_server/map.py +++ b/feature_server/map.py @@ -112,10 +112,10 @@ def __init__(self, name = "pyspades"): self.name = name def get_seed(self): - if self.seed is None: - random.seed() - self.seed = random.randint(0, math.pow(2, 31)) - return self.seed + if self.seed is not None: + return self.seed + random.seed() + return random.randint(0, math.pow(2, 31)) def get_map_filename(self, load_dir = DEFAULT_LOAD_DIR): return os.path.join(load_dir, '%s.vxl' % self.name) From 85edb0f8ddc7ce74fc0cdf5ba8d02a70d38b6980 Mon Sep 17 00:00:00 2001 From: matpow2 Date: Mon, 27 Aug 2012 22:55:01 +0200 Subject: [PATCH 26/36] Oops --- feature_server/map.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/feature_server/map.py b/feature_server/map.py index cd6a7956..b8e1b3be 100644 --- a/feature_server/map.py +++ b/feature_server/map.py @@ -49,10 +49,11 @@ def __init__(self, rot_info, load_dir = DEFAULT_LOAD_DIR): self.load_information(rot_info, load_dir) if self.gen_script: - self.name = '%s #%s' % (rot_info.name, rot_info.get_seed()) + seed = rot_info.get_seed() + self.name = '%s #%s' % (rot_info.name, seed) print "Generating map '%s'..." % self.name - random.seed(rot_info.get_seed()) - self.data = self.gen_script(rot_info.name, rot_info.get_seed()) + random.seed(seed) + self.data = self.gen_script(rot_info.name, seed) else: print "Loading map '%s'..." % self.name self.load_vxl(rot_info, load_dir) From 63aa5d588046fa5749f150fc880513e6273a2942 Mon Sep 17 00:00:00 2001 From: infogulch Date: Fri, 31 Aug 2012 17:29:20 -0500 Subject: [PATCH 27/36] Fix #281: Invalid number of args to commands is a separate error. If a command throws an actual error it is now printed to the console. --- feature_server/commands.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/feature_server/commands.py b/feature_server/commands.py index 5cb01e73..7884cae4 100644 --- a/feature_server/commands.py +++ b/feature_server/commands.py @@ -22,6 +22,7 @@ from pyspades.server import parse_command from twisted.internet import reactor from map import check_rotation +import inspect commands = {} aliases = {} @@ -48,6 +49,7 @@ def new_func(connection, *arg, **kw): return func(connection, *arg, **kw) new_func.func_name = func.func_name new_func.user_types = user_types + new_func.argspec = inspect.getargspec(func) return new_func def has_rights(f, connection): @@ -933,6 +935,8 @@ def add(func, name = None): if name is None: name = func.func_name name = name.lower() + if not hasattr(func, 'argspec'): + func.argspec = inspect.getargspec(func) add_rights(name, *getattr(func, 'user_types', ())) commands[name] = func try: @@ -989,14 +993,21 @@ def handle_command(connection, command, parameters): command_func = commands[command] except KeyError: return # 'Invalid command' + mn = len(command_func.argspec.args) - 1 - len(command_func.argspec.defaults or ()) + mx = len(command_func.argspec.args) - 1 if command_func.argspec.varargs is None else None + lp = len(parameters) + if lp < mn or mx is not None and lp > mx: + return 'Invalid number of arguments for %s' % command try: if not has_rights(command_func, connection): return "You can't use this command" return command_func(connection, *parameters) except KeyError: return # 'Invalid command' - except TypeError: - return 'Invalid number of arguments for %s' % command + except TypeError, t: + print 'Command', command, 'failed with args:', parameters + print t + return 'Command failed' except InvalidPlayer: return 'No such player' except InvalidTeam: From 0276ec99a79f34ad133f829708d93c6a5cc914c6 Mon Sep 17 00:00:00 2001 From: infogulch Date: Sat, 10 Nov 2012 12:58:03 -0600 Subject: [PATCH 28/36] Rearrange imports in commands.py --- feature_server/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature_server/commands.py b/feature_server/commands.py index 7884cae4..aaeb3463 100644 --- a/feature_server/commands.py +++ b/feature_server/commands.py @@ -16,13 +16,13 @@ # along with pyspades. If not, see . import math +import inspect from random import choice from pyspades.constants import * from pyspades.common import prettify_timespan from pyspades.server import parse_command from twisted.internet import reactor from map import check_rotation -import inspect commands = {} aliases = {} From 6d889e45946a55e5c52a877a8702691304208add Mon Sep 17 00:00:00 2001 From: infogulch Date: Sat, 10 Nov 2012 13:00:31 -0600 Subject: [PATCH 29/36] Adjust teleport location slightly --- feature_server/commands.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/feature_server/commands.py b/feature_server/commands.py index aaeb3463..6ada2955 100644 --- a/feature_server/commands.py +++ b/feature_server/commands.py @@ -477,7 +477,8 @@ def teleport(connection, player1, player2 = None, silent = False): silent = silent or player.invisible message = '%s ' + ('silently ' if silent else '') + 'teleported to %s' message = message % (player.name, target.name) - player.set_location(target.get_location()) + x, y, z = target.get_location() + player.set_location(((x-0.5), (y-0.5), (z+0.5))) if silent: connection.protocol.irc_say('* ' + message) else: From bf052f242a6931e8b24213c5df07d11aa7e9c929 Mon Sep 17 00:00:00 2001 From: infogulch Date: Sat, 10 Nov 2012 13:01:58 -0600 Subject: [PATCH 30/36] Add go_to_silent (gotos) command --- feature_server/commands.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/feature_server/commands.py b/feature_server/commands.py index 6ada2955..0b42839d 100644 --- a/feature_server/commands.py +++ b/feature_server/commands.py @@ -508,6 +508,13 @@ def go_to(connection, value): raise KeyError() move(connection, connection.name, value, silent = connection.invisible) +@name('gotos') +@admin +def go_to_silent(connection, value): + if connection not in connection.protocol.players: + raise KeyError() + move(connection, connection.name, value, True) + @admin def move(connection, player, value, silent = False): player = get_player(connection.protocol, player) @@ -902,6 +909,7 @@ def weapon(connection, value): teleport, tpsilent, go_to, + go_to_silent, move, unstick, where, From 11604bbe9f85b9211d8e572da144e0a5eec96a94 Mon Sep 17 00:00:00 2001 From: infogulch Date: Sat, 10 Nov 2012 13:03:08 -0600 Subject: [PATCH 31/36] Add godsilent (gods) command. --- feature_server/commands.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/feature_server/commands.py b/feature_server/commands.py index 0b42839d..14c761d7 100644 --- a/feature_server/commands.py +++ b/feature_server/commands.py @@ -545,8 +545,9 @@ def where(connection, value = None): return '%s is in %s (%s, %s, %s)' % (connection.name, to_coordinates(x, y), int(x), int(y), int(z)) +@alias('gods') @admin -def god(connection, value = None): +def godsilent(connection, value = None): if value is not None: connection = get_player(connection.protocol, value) elif connection not in connection.protocol.players: @@ -556,6 +557,11 @@ def god(connection, value = None): connection.god_build = connection.god else: connection.god_build = False + return 'You have entered god mode.' + +@admin +def god(connection, value = None): + godsilent(connection, value) if connection.god: message = '%s entered GOD MODE!' % connection.name else: @@ -914,6 +920,7 @@ def weapon(connection, value): unstick, where, god, + godsilent, god_build, fly, invisible, From 9beae4a0a70747d6432da8019a9e3c9bc5cf7a25 Mon Sep 17 00:00:00 2001 From: infogulch Date: Sat, 10 Nov 2012 13:06:23 -0600 Subject: [PATCH 32/36] handle_command(): parameter length check cleanup --- feature_server/commands.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/feature_server/commands.py b/feature_server/commands.py index 14c761d7..e1b2fe6e 100644 --- a/feature_server/commands.py +++ b/feature_server/commands.py @@ -1009,10 +1009,12 @@ def handle_command(connection, command, parameters): command_func = commands[command] except KeyError: return # 'Invalid command' - mn = len(command_func.argspec.args) - 1 - len(command_func.argspec.defaults or ()) - mx = len(command_func.argspec.args) - 1 if command_func.argspec.varargs is None else None - lp = len(parameters) - if lp < mn or mx is not None and lp > mx: + aspec = command_func.argspec + min_params = len(aspec.args) - 1 - len(aspec.defaults or ()) + max_params = len(aspec.args) - 1 if aspec.varargs is None else None + len_params = len(parameters) + if (len_params < min_params + or max_params is not None and len_params > max_params): return 'Invalid number of arguments for %s' % command try: if not has_rights(command_func, connection): @@ -1020,7 +1022,7 @@ def handle_command(connection, command, parameters): return command_func(connection, *parameters) except KeyError: return # 'Invalid command' - except TypeError, t: + except TypeError as t: print 'Command', command, 'failed with args:', parameters print t return 'Command failed' From 49111016306d55da21adcab8af025054b4c57585 Mon Sep 17 00:00:00 2001 From: infogulch Date: Sat, 10 Nov 2012 14:02:15 -0600 Subject: [PATCH 33/36] Remove unnecessary debug_handle_command(). handle_command() no longer 'eats' errors --- feature_server/commands.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/feature_server/commands.py b/feature_server/commands.py index e1b2fe6e..a06bd795 100644 --- a/feature_server/commands.py +++ b/feature_server/commands.py @@ -1033,25 +1033,6 @@ def handle_command(connection, command, parameters): except ValueError: return 'Invalid parameters' -def debug_handle_command(connection, command, parameters): - # use this when regular handle_command eats errors - if connection in connection.protocol.players: - connection.send_chat("Commands are in DEBUG mode") - command = command.lower() - try: - command = aliases[command] - except KeyError: - pass - try: - command_func = commands[command] - except KeyError: - return # 'Invalid command' - if not has_rights(command_func, connection): - return "You can't use this command" - return command_func(connection, *parameters) - -# handle_command = debug_handle_command - def handle_input(connection, input): # for IRC and console return handle_command(connection, *parse_command(input)) From 6d65eec6cdda095e4ce19ed48ddb568846ecd58c Mon Sep 17 00:00:00 2001 From: infogulch Date: Mon, 18 Mar 2013 14:14:00 -0500 Subject: [PATCH 34/36] Change IP Getter to icanhazip.com --- pyspades/master.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pyspades/master.py b/pyspades/master.py index c7b86957..9dea4384 100644 --- a/pyspades/master.py +++ b/pyspades/master.py @@ -103,6 +103,10 @@ def on_disconnect(self): from pyspades.web import getPage IP_GETTER = 'http://services.buildandshoot.com/getip' +# other tools: +# http://www.domaintools.com/research/my-ip/myip.xml +# http://checkip.dyndns.com/ +# http://icanhazip.com/ def get_external_ip(interface = ''): return getPage(IP_GETTER, bindAddress = (interface, 0)) @@ -112,4 +116,4 @@ def get_master_connection(protocol): connection = protocol.connect(MasterConnection, HOST, PORT, MASTER_VERSION) connection.server_protocol = protocol connection.defer = defer - return defer \ No newline at end of file + return defer From 74bb05d70030ee7390f1e425b0f214184b6572cc Mon Sep 17 00:00:00 2001 From: infogulch Date: Fri, 23 Nov 2012 18:51:01 -0600 Subject: [PATCH 35/36] Count all destroyed blocks, incl. fallen blocks New property is `connection.total_blocks_destroyed`. It should only be read from, not written to. If you want to know how many blocks was destroyed at one specific time, save the current count in a temporary variable and take the difference. --- pyspades/server.py | 24 ++++++++++++++---------- pyspades/vxl.pxd | 4 ++-- pyspades/vxl.pyx | 9 +++++---- pyspades/vxl_c.cpp | 7 ++++--- 4 files changed, 25 insertions(+), 19 deletions(-) diff --git a/pyspades/server.py b/pyspades/server.py index ab707b49..12d3d25a 100644 --- a/pyspades/server.py +++ b/pyspades/server.py @@ -206,6 +206,7 @@ def __init__(self, *arg, **kw): BaseConnection.__init__(self, *arg, **kw) protocol = self.protocol address = self.peer.address + self.total_blocks_removed = 0 self.address = (address.host, address.port) self.respawn_time = protocol.respawn_time self.rapids = SlidingWindow(RAPID_WINDOW_ENTRIES) @@ -530,16 +531,17 @@ def loader_received(self, loader): if self.on_block_destroy(x, y, z, value) == False: return elif value == DESTROY_BLOCK: - if map.destroy_point(x, y, z): + count = map.destroy_point(x, y, z) + if count: + self.total_blocks_removed += count self.blocks = min(50, self.blocks + 1) self.on_block_removed(x, y, z) elif value == SPADE_DESTROY: - if map.destroy_point(x, y, z): - self.on_block_removed(x, y, z) - if map.destroy_point(x, y, z + 1): - self.on_block_removed(x, y, z + 1) - if map.destroy_point(x, y, z - 1): - self.on_block_removed(x, y, z - 1) + for xyz in ((x, y, z), (x, y, z + 1), (x, y, z - 1)): + count = map.destroy_point(*xyz) + if count: + self.total_blocks_removed += count + self.on_block_removed(*xyz) self.last_block_destroy = reactor.seconds() block_action.x = x block_action.y = y @@ -629,7 +631,7 @@ def check_refill(self): self.last_refill = reactor.seconds() if self.on_refill() != False: self.refill() - + def get_location(self): position = self.world_object.position return position.x, position.y, position.z @@ -665,7 +667,7 @@ def set_location_safe(self, location, center = True): y = y + pos_table[modpos][1] z = z + pos_table[modpos][2] self.set_location((x, y, z)) - + def set_location(self, location = None): if location is None: # used for rubberbanding @@ -1037,7 +1039,9 @@ def grenade_exploded(self, grenade): for nade_x in xrange(x - 1, x + 2): for nade_y in xrange(y - 1, y + 2): for nade_z in xrange(z - 1, z + 2): - if map.destroy_point(nade_x, nade_y, nade_z): + count = map.destroy_point(nade_x, nade_y, nade_z) + if count: + self.total_blocks_removed += count self.on_block_removed(nade_x, nade_y, nade_z) block_action.x = x block_action.y = y diff --git a/pyspades/vxl.pxd b/pyspades/vxl.pxd index 4f79ad5b..3620053b 100644 --- a/pyspades/vxl.pxd +++ b/pyspades/vxl.pxd @@ -39,8 +39,8 @@ cdef class VXLData: cpdef bint has_neighbors(self, int x, int y, int z) cpdef bint is_surface(self, int x, int y, int z) cpdef list get_neighbors(self, int x, int y, int z) - cpdef bint check_node(self, int x, int y, int z, bint destroy = ?) + cpdef int check_node(self, int x, int y, int z, bint destroy = ?) cpdef bint build_point(self, int x, int y, int z, tuple color) cpdef bint set_column_fast(self, int x, int y, int start_z, int end_z, int end_color_z, int color) - cpdef update_shadows(self) \ No newline at end of file + cpdef update_shadows(self) diff --git a/pyspades/vxl.pyx b/pyspades/vxl.pyx index 97a17492..8a7d4967 100644 --- a/pyspades/vxl.pyx +++ b/pyspades/vxl.pyx @@ -118,16 +118,17 @@ cdef class VXLData: def destroy_point(self, int x, int y, int z): if not self.get_solid(x, y, z) or z >= 62: - return False + return 0 set_point(x, y, z, self.map, 0, 0) + count = 1 start = time.time() for node_x, node_y, node_z in self.get_neighbors(x, y, z): if node_z < 62: - self.check_node(node_x, node_y, node_z, True) + count += self.check_node(node_x, node_y, node_z, True) taken = time.time() - start if taken > 0.1: print 'destroying block at', x, y, z, 'took:', taken - return True + return count def remove_point(self, int x, int y, int z): if is_valid_position(x, y, z): @@ -166,7 +167,7 @@ cdef class VXLData: neighbors.append((node_x, node_y, node_z)) return neighbors - cpdef bint check_node(self, int x, int y, int z, bint destroy = False): + cpdef int check_node(self, int x, int y, int z, bint destroy = False): return check_node(x, y, z, self.map, destroy) cpdef bint build_point(self, int x, int y, int z, tuple color): diff --git a/pyspades/vxl_c.cpp b/pyspades/vxl_c.cpp index 20641a59..08c58413 100644 --- a/pyspades/vxl_c.cpp +++ b/pyspades/vxl_c.cpp @@ -149,13 +149,13 @@ int check_node(int x, int y, int z, MapData * map, int destroy) z = current_node->z; if (z >= 62) { marked.clear(); - return 1; + return 0; } x = current_node->x; y = current_node->y; int i = get_pos(x, y, z); - + // already visited? pair::iterator, bool> ret; ret = marked.insert(i); @@ -180,8 +180,9 @@ int check_node(int x, int y, int z, MapData * map, int destroy) } } + int ret = (int)marked.size(); marked.clear(); - return 0; + return ret; } // write_map/save_vxl function from stb/nothings - thanks a lot for the From d02e2f392d7420ef523ff01f5b9091dceaf22f4a Mon Sep 17 00:00:00 2001 From: Nate Shoffner Date: Wed, 6 Nov 2013 11:18:05 -0500 Subject: [PATCH 36/36] Default config refactoring. --- feature_server/config.txt.default | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature_server/config.txt.default b/feature_server/config.txt.default index 795d30d7..252df8aa 100644 --- a/feature_server/config.txt.default +++ b/feature_server/config.txt.default @@ -142,4 +142,4 @@ "load_saved_map" : false, "rollback_on_game_end" : false, "afk_time_limit" : 30 -} \ No newline at end of file +}