diff --git a/contrib/scripts/arena.py b/contrib/scripts/arena.py index ef112f9b..94e300e4 100644 --- a/contrib/scripts/arena.py +++ b/contrib/scripts/arena.py @@ -13,19 +13,27 @@ # 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 '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) # 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)) # } @@ -36,29 +44,40 @@ from twisted.internet import reactor 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 = 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. +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 -MAP_CHANGE_DELAY = 30.0 +MAP_CHANGE_DELAY = 25.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): @@ -259,12 +278,30 @@ 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 > self.protocol.arena_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_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: @@ -279,7 +316,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): @@ -304,6 +341,8 @@ class ArenaProtocol(protocol): arena_take_flag = False 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: @@ -332,10 +371,15 @@ 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 and TEAM_COLOR_TIME > 0: + 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 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() @@ -343,6 +387,13 @@ def arena_win(self, team, killer = None): 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: return @@ -365,6 +416,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 @@ -374,14 +426,20 @@ 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.') + 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: @@ -396,6 +454,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 @@ -414,9 +473,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): diff --git a/feature_server/commands.py b/feature_server/commands.py index 906829ef..a06bd795 100644 --- a/feature_server/commands.py +++ b/feature_server/commands.py @@ -16,14 +16,18 @@ # 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 +commands = {} +aliases = {} +rights = {} + class InvalidPlayer(Exception): pass @@ -33,15 +37,26 @@ 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.user_types = user_types + new_func.argspec = inspect.getargspec(func) return new_func +def has_rights(f, connection): + return not hasattr(f, 'user_types') or f.func_name in connection.rights + def admin(func): - return restrict(func, ('admin',)) + return restrict(func, 'admin') def name(name): def dec(func): @@ -90,6 +105,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): @@ -194,12 +211,18 @@ 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) - connection.protocol.send_chat(message, irc = True) + if connection is not player: + message = '%s killed %s' % (connection.name, player.name) + connection.protocol.send_chat(message, irc = True) @admin def heal(connection, player = None): @@ -301,7 +324,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 +335,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 +351,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') @@ -450,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: @@ -480,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) @@ -510,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: @@ -521,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: @@ -874,10 +915,12 @@ def weapon(connection, value): teleport, tpsilent, go_to, + go_to_silent, move, unstick, where, god, + godsilent, god_build, fly, invisible, @@ -901,10 +944,6 @@ def weapon(connection, value): mapname ] -commands = {} -aliases = {} -rights = {} - def add(func, name = None): """ Function to add a command from scripts @@ -912,13 +951,9 @@ 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]) + if not hasattr(func, 'argspec'): + func.argspec = inspect.getargspec(func) + add_rights(name, *getattr(func, 'user_types', ())) commands[name] = func try: for alias in func.aliases: @@ -974,15 +1009,23 @@ def handle_command(connection, command, parameters): command_func = commands[command] except KeyError: return # 'Invalid command' + 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 (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' - except TypeError: - return 'Invalid number of arguments for %s' % command + except TypeError as t: + print 'Command', command, 'failed with args:', parameters + print t + return 'Command failed' except InvalidPlayer: return 'No such player' except InvalidTeam: @@ -990,26 +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 (hasattr(command_func, 'user_types') and - command_func.func_name not in connection.rights): - 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)) 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 +} 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/feature_server/map.py b/feature_server/map.py index 33adb9d3..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) @@ -112,10 +113,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) diff --git a/feature_server/run.py b/feature_server/run.py index 8c1976dd..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): @@ -668,7 +672,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) 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 diff --git a/feature_server/scripts/platform.py b/feature_server/scripts/platform.py index 57c69324..2f85ad29 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. @@ -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 """ @@ -171,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? @@ -192,12 +197,14 @@ 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 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" @@ -239,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 ' @@ -326,6 +333,7 @@ } ACTION_RAY_LENGTH = 8.0 +ACTION_RAY_LENGTH_LONG = 32.0 ACTION_COOLDOWN = 0.25 def parseargs(signature, args): @@ -356,6 +364,14 @@ def save(connection): connection.protocol.dump_platform_json() return S_SAVED +@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 + @alias('p') @name('platform') @admin @@ -532,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 @@ -692,7 +708,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 +1113,7 @@ class Platform(BaseObject): frozen = False mode = None busy = False + running = False delay_call = None bound_triggers = None @@ -1107,7 +1124,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 +1151,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 +1167,23 @@ 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 + self.protocol.running_platforms.add(self) def cycle(self): if self.frozen: @@ -1178,6 +1195,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 +1218,13 @@ 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 + self.protocol.running_platforms.discard(self) if self.bound_triggers: for trigger in self.bound_triggers: trigger.callback(self) @@ -1267,7 +1285,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 +1714,7 @@ class PlatformConnection(connection): last_action = None previous_button = None previous_platform = None + reach = ACTION_RAY_LENGTH def on_reset(self): if self.states: @@ -1788,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 @@ -1795,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 @@ -1815,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) @@ -1823,6 +1845,11 @@ def on_world_update(self): for player in self.players.itervalues(): for trigger in self.position_triggers: trigger.callback(player) + 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() protocol.on_world_update(self) def get_platform_json_path(self): 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: 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 diff --git a/pyspades/server.py b/pyspades/server.py index 322def0f..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) @@ -235,9 +236,13 @@ 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 + + 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: name = contained.name @@ -526,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 @@ -602,8 +608,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): @@ -623,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 @@ -659,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 @@ -1007,9 +1015,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: @@ -1031,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 09f6458e..3620053b 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 @@ -38,7 +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) \ No newline at end of file + int end_z, int end_color_z, int color) + cpdef update_shadows(self) diff --git a/pyspades/vxl.pyx b/pyspades/vxl.pyx index 21103d03..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): @@ -193,6 +194,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..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 @@ -189,8 +190,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; @@ -373,6 +374,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/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 diff --git a/tools/editor/run.py b/tools/editor/run.py index 79d78ac0..8e474f99 100644 --- a/tools/editor/run.py +++ b/tools/editor/run.py @@ -24,8 +24,10 @@ import os sys.path.append('../../') +import re 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 +354,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 +415,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 +508,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 +688,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 +715,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 +731,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 +739,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 +757,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 +781,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 +792,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,18 +810,20 @@ 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) + 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 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) + new_image.save(path + format(z, '02d') + ext) def copy_selected(self): self.clipboard.setImage(self.edit_widget.image) @@ -828,11 +851,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 +879,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 +914,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 +937,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: @@ -949,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()