def _distance_between_coordinates(lat1, lon1, lat2, lon2): # https://gis.stackexchange.com/questions/61924/python-gdal-degrees-to-meters-without-reprojecting # Calculate the great circle distance in meters between two points on the earth (specified in decimal degrees) next_level() message( "calculating distance between coordinates...", '(' + str(lat1) + ', ' + str(lon1) + ') - (' + str(lat2) + ', ' + str(lon2) + ')', 5) # convert decimal degrees to radians r_lat1, r_lon1, r_lat2, r_lon2 = math.radians(lat1), math.radians( lon1), math.radians(lat2), math.radians(lon2) # haversine formula d_r_lon = r_lon2 - r_lon1 d_r_lat = r_lat2 - r_lat1 a = math.sin( d_r_lat / 2.0)**2 + math.cos(r_lat1) * math.cos(r_lat2) * math.sin( d_r_lon / 2.0)**2 c = 2.0 * math.asin(math.sqrt(a)) earth_radius = 6371000.0 # radius of the earth in m dist = int(earth_radius * c) next_level() message("distance between coordinates calculated", str(dist) + " meters", 5) back_level() back_level() return dist
def tryDropItem(self, item): """ Player attempts to drop an item. This function is meant to be called from the GUI. """ if isinstance(item, Equipment): if item.isEquiped: message("You can't drop an equiped item.") return self.dropItem(item)
def unEquipItem(self, item): """ basic implementation of equiping, doesn't take into account equipment slots. Should be overridden in subclass implementations. """ #can only unequip if item is equiped if item in self.equipedItems: self.equipedItems.remove(item) item.isEquiped = False message(self.name.capitalize() + ' unequips a ' + item.name + '.', "GAME")
def applyTo(self, target): ''' Confuse effect will be applied to target monster. :target: Monster object :return: None ''' if not isinstance(target, Actors.Monster): raise GameError("Can not apply confuse effect to " + str(target)) confusedTurns = self.effectDuration AI.ConfusedMonsterAI(self, target, confusedTurns) target.level.game.activeEffects.append(self) message(target.name + ' is confused for ' + str(confusedTurns) + ' turns.', "GAME")
def pickUpItem(self, item): """ Make this character pick up an item. Arguments item - the item to be picked up """ #remove the item from its tile and level item.removeFromLevel() #add the item to the inventory of this character self.addItem(item) #message message(self.name.capitalize() + ' picks up a ' + item.name + '.', "GAME")
def equipItem(self, item): """ basic implementation of equiping, doesn't take into account equipment slots. Should be overridden in subclass implementations. """ #can only equip if item is in inventory if item in self.inventory.items: #can only equip if not yet equiped if item not in self.equipedItems: self.equipedItems.append(item) item.isEquiped = True message(self.name.capitalize() + ' equips a ' + item.name + '.', "GAME")
def followPortal(self, portal): """ Send player through specified portal. """ #Game message message(portal.message, "GAME") #Move the player to the destination destinationLevel = portal.destinationPortal.level destinationTile = portal.destinationPortal.tile self.moveToLevel(destinationLevel, destinationTile) #change the current level of the game to the destinationlevel myGame = destinationLevel.game myGame.currentLevel = destinationLevel
def takeHeal(self, amount, healer): """ function to heal a given amount of hitpoints arguments amount - the number of hitpoints to heal healer - the source of teh healing """ #heal by the given amount if amount > 0: self.currentHitPoints += amount message(self.name.capitalize() + ' gains ' + str(amount) + ' hitpoints from a ' + healer.name + '.', "GAME")
def attack(self, target): """ Attack another Character Arguments target - the Character to be attacked """ # Check if the attack hits hitRoll = rollHitDie("1d100") # In case of an equal accuracy and dodge rating there is a 50% chance to hit toHit = 100 - (50 + self.accuracy - target.dodge) message(self.name.capitalize() + ' attacks ' + target.name + ': ' + str(hitRoll) + ' vs ' + str(toHit), "COMBAT") if hitRoll < toHit: # Miss, no damage message(self.name.capitalize() + ' attacks ' + target.name + ' but misses!', "COMBAT") else: # Hit, there will be damage, bonusDamage depends on how strongly the hit connects bonusDamagePercent = (hitRoll - toHit) / 100.0 damagePercent = 1 + bonusDamagePercent # targets armor neutralizes part of the damage damage = int(damagePercent * self.damage) - target.armor if damage > 0: message(self.name.capitalize() + ' attacks ' + target.name + ' and hits for ' + str(damage) + ' Damage (' + str(damagePercent) +' damage factor)', "COMBAT") target.takeDamage(damage, self) else: message(self.name.capitalize() + ' attacks ' + target.name + ' and hits but it has no effect.', "COMBAT")
def levelUp(self): ''' Increase level of this player ''' message("You feel stronger!", "GAME") self._playerLevel += 1 self._nextLevelXp = CONSTANTS.GAME_XP_BASE + CONSTANTS.GAME_XP_BASE * CONSTANTS.GAME_XP_FACTOR * (self.playerLevel * self.playerLevel - 1) self._baseAccuracy += CONSTANTS.GAME_PLAYER_LEVEL_ACCURACY self._baseDodge += CONSTANTS.GAME_PLAYER_LEVEL_DODGE self._baseDamage += CONSTANTS.GAME_PLAYER_LEVEL_DAMAGE self._baseArmor += CONSTANTS.GAME_PLAYER_LEVEL_ARMOR self._baseBody += CONSTANTS.GAME_PLAYER_LEVEL_BODY self._baseMind += CONSTANTS.GAME_PLAYER_LEVEL_MIND
def __init__(self): if Options.config['get_geonames_online']: Geonames._base_nearby_url = "{}findNearbyJSON?lat={{}}&lng={{}}&featureClass=P&username={}&lang={}".format( self.GEONAMES_API, Options.config['geonames_user'], Options.config['geonames_language']) if self.cities == []: next_level() message("reading and processing local geonames files", "", 5) territories_file = os.path.join(os.path.dirname(__file__), 'geonames/territories.json') countries_file = os.path.join(os.path.dirname(__file__), 'geonames/countries.json') cities_file = os.path.join(os.path.dirname(__file__), 'geonames/cities1000.txt') with open(territories_file, 'r') as territories_file_p: territories = json.load(territories_file_p) with open(countries_file, 'r') as countries_file_p: countries = json.load(countries_file_p) with open(cities_file, 'r') as all_cities: for line in all_cities: col = line.split('\t') country_code = col[8] state_code = col[10] try: country_name = countries[country_code] except KeyError: country_name = '' try: state_name = territories[country_code + '.' + state_code] except KeyError: state_name = '' city_line = { 'country_name': country_name, 'country_code': country_code, 'region_name': state_name, 'region_code': state_code, 'place_name': col[1], 'place_code': col[0], 'latitude': float(col[4]), 'longitude': float(col[5]) } self.cities.append(city_line) next_level() message("local geonames files read and processed", "", 5) back_level() back_level()
def takeDamage(self, amount, attacker): """ function to take damage from an attacker arguments damage - the incoming damage attacker - the attacking Actor """ if self.state == Character.ACTIVE: #apply damage if possible if amount > 0: self.currentHitPoints -= amount #check for death if self.currentHitPoints <= 0: message(self.name.capitalize() + ' is killed!', "COMBAT") self._killedBy(attacker)
def dropItem(self, item): """ Make this character drop an item on the current tile. Arguments item - the item to be dropped """ #unequip it if required if item in self.equipedItems: self.unEquipItem(item) #if it is in the inventory remove it if item in self.inventory.items: self.inventory.remove(item) #add it to the current tile of the character item.moveToLevel(self.level, self.tile) #message message(self.name.capitalize() + ' drops a ' + item.name + '.', "GAME")
def _killedBy(self, attacker): """ This function handles the death of this Character """ if self.state == Character.ACTIVE: if type(attacker) is Player: #yield experience to the player message(attacker.name + ' gains ' + str(self.xpValue) + ' XP.', "GAME") attacker.gainXp(self.xpValue) if type(attacker) is Monster: if attacker.killedByText != '': message(attacker.killedByText, "GAME") #transform this character into a corpse and remove AI self._char = '%' self._AI = None self._name = self.name + ' corpse' self._state = Character.DEAD
def tick(self): ''' Apply one tick of damage :return: None ''' # Update effectduration if self.effectDuration == 0: return self.effectDuration -= 1 #find all targets in range self._actors = [] for tile in self.tiles: for actor in tile.actors: self.actors.append(actor) #apply damage to every target damageAmount = rollHitDie(self.effectHitDie) for target in self.actors: message(self.source.name.capitalize() + ' hits ' + target.name + ' for ' + str(damageAmount) + ' Damage.', "GAME") target.takeDamage(damageAmount, self.source.owner)
def main(): # @python2 if sys.version_info < (3, ): reload(sys) sys.setdefaultencoding("UTF-8") if len(sys.argv) != 3 and len(sys.argv) != 2: print("usage: {0} ALBUM_PATH CACHE_PATH - or {1} CONFIG_FILE".format( sys.argv[0], sys.argv[0])) return Options.initialize_opencv() Options.get_options() try: os.umask(0o02) TreeWalker() report_times(True) message(" The end! ", "", 3) except KeyboardInterrupt: message("keyboard", "CTRL+C pressed, quitting.") sys.exit(-97)
def lookup_nearby_place(latitude, longitude): """ Looks up places near a specific geographic location """ next_level() message("looking for geonames in cache...", "", 5) for _ in range(len(Geonames.geonames_cache) - 1, -1, -1): ((c_latitude, c_longitude), result) = Geonames.geonames_cache[_] distance = Geonames._distance_between_coordinates( c_latitude, c_longitude, latitude, longitude) if distance < Geonames.MAX_DISTANCE_METERS: # ok with value from cache! next_level() message("geoname got from cache", "", 5) back_level() back_level() # # add to cache only if not too close to existing point # if distance > Geonames.MAX_DISTANCE_METERS / 2.0: # Geonames.geonames_cache[(latitude, longitude)] = result return result next_level() message("geonames not found in cache", "", 5) back_level() got = False if Options.config['get_geonames_online']: message("getting geonames online...", "", 5) # get country, region (state for federal countries), and place try_number = 0 while True: response = requests.get( Geonames._base_nearby_url.format(str(latitude), str(longitude))) try: result = Geonames._decode_nearby_place(response.text) if isinstance(result, dict): got = True next_level() message("geonames got from geonames.org online", "", 5) back_level() break else: # result is a number, which means that the request to geonames.org produced an error try_number += 1 next_level() message( "geonames.org returned error code, retrying...", "try = " + str(try_number) + ", error code = " + str(result), 5) back_level() except json.decoder.JSONDecodeError: # error when decoding # once the json.loads() function inside _decode_nearby_place() throwed the error: # json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0) try_number += 1 if try_number <= 3: next_level() message( "error deconding geonames.org response, retrying...", "try = " + str(try_number), 5) back_level() if try_number == 3: next_level() message("three errors", "giving up", 5) back_level() break if not got: # get country, region (state for federal countries), and place message("getting geonames from local files...", "", 5) result = min( [city for city in Geonames.cities], key=lambda c: Geonames.quick_distance_between_coordinates( c['latitude'], c['longitude'], latitude, longitude)) result['distance'] = Geonames._distance_between_coordinates( latitude, longitude, result['latitude'], result['longitude']) next_level() message("geonames got from local files", "", 5) back_level() # add to cache message("adding geonames to cache...", "", 5) Geonames.geonames_cache.append(((latitude, longitude), result)) next_level() message("geonames added to cache", "", 5) back_level() back_level() return result
def initialize_opencv(): global face_cascade, eye_cascade try: import cv2 message("importer", "opencv library available, using it!", 3) next_level() FACE_CONFIG_FILE = "haarcascade_frontalface_default.xml" message("looking for file...", FACE_CONFIG_FILE + " in /usr/share", 5) face_config_file_with_path = find_in_usr_share(FACE_CONFIG_FILE) if not face_config_file_with_path: message("face xml file not found", FACE_CONFIG_FILE + " not found in /usr/share", 5) message("looking for file...", FACE_CONFIG_FILE + " in /", 5) face_config_file_with_path = find(FACE_CONFIG_FILE) if not face_config_file_with_path: next_level() message("face xml file not found", FACE_CONFIG_FILE, 5) back_level() config['cv2_installed'] = False else: face_cascade = cv2.CascadeClassifier(face_config_file_with_path) next_level() message("face xml file found and initialized:", face_config_file_with_path, 5) back_level() EYE_CONFIG_FILE = "haarcascade_eye.xml" message("looking for file...", EYE_CONFIG_FILE, 5) eye_config_file_with_path = find_in_usr_share(EYE_CONFIG_FILE) if not eye_config_file_with_path: eye_config_file_with_path = find(EYE_CONFIG_FILE) if not eye_config_file_with_path: next_level() message("eyes xml file not found", EYE_CONFIG_FILE, 5) back_level() config['cv2_installed'] = False else: eye_cascade = cv2.CascadeClassifier(eye_config_file_with_path) next_level() message("found and initialized:", eye_config_file_with_path, 5) back_level() back_level() except ImportError: config['cv2_installed'] = False message("importer", "No opencv library available, not using it", 2)
def get_options(): project_dir = os.path.dirname(os.path.realpath(os.path.join(__file__, ".."))) default_config_file = os.path.join(project_dir, "myphotoshare.conf.defaults") default_config = configparser.ConfigParser() default_config.readfp(open(default_config_file, "r")) usr_config = configparser.ConfigParser() usr_config.add_section("options") for option in default_config.options('options'): usr_config.set("options", option, default_config.get("options", option)) if len(sys.argv) == 2: # 1 arguments: the config files # which modifies the default options usr_config.readfp(open(sys.argv[1], "r")) else: usr_config.set('options', 'album_path', sys.argv[1]) usr_config.set('options', 'cache_path', sys.argv[2]) message("Options", "asterisk denotes options changed by config file", 0) next_level() # pass config values to a dict, because ConfigParser objects are not reliable for option in default_config.options('options'): if option in ('max_verbose', 'photo_map_zoom_level', 'jpeg_quality', 'video_crf', 'thumb_spacing', 'album_thumb_size', 'media_thumb_size', 'big_virtual_folders_threshold', 'max_search_album_number', # the following option will be converted to integer further on 'num_processors', 'max_album_share_thumbnails_number', 'min_album_thumbnail', 'piwik_id' ): try: if option != 'piwik_id' or config['piwik_server']: # piwik_id must be evaluated here because otherwise an error is produced if it's not set config[option] = usr_config.getint('options', option) else: config[option] = "" except configparser.Error: next_level() message("WARNING: option " + option + " in user config file", "is not integer, using default value", 2) back_level() config[option] = default_config.getint('options', option) elif option in ('follow_symlinks', 'checksum', 'different_album_thumbnails', 'albums_slide_style', 'show_media_names_below_thumbs', 'show_album_names_below_thumbs', 'show_album_media_count', 'persistent_metadata', 'default_album_name_sort', 'default_media_name_sort', 'default_album_reverse_sort', 'default_media_reverse_sort', 'recreate_fixed_height_thumbnails', 'get_geonames_online', 'show_faces', 'use_stop_words' ): try: config[option] = usr_config.getboolean('options', option) except ValueError: next_level() message("WARNING: option " + option + " in user config file", "is not boolean, using default value", 2) back_level() config[option] = default_config.getboolean('options', option) elif option in ('reduced_sizes', 'map_zoom_levels', 'metadata_tools_preference'): config[option] = ast.literal_eval(usr_config.get('options', option)) elif option in ('mobile_thumbnail_factor', 'face_cascade_scale_factor'): config[option] = usr_config.getfloat('options', option) if config[option] < 1: config[option] = 1 else: config[option] = usr_config.get('options', option) option_value = str(config[option]) option_length = len(option_value) max_length = 40 spaces = "" #pylint for _ in range(max_length - option_length): spaces += " " max_spaces = "" #pylint for _ in range(max_length): max_spaces += " " default_option_value = str(default_config.get('options', option)) default_option_length = len(default_option_value) default_spaces = "" for _ in range(max_length - default_option_length - 2): default_spaces += " " if default_config.get('options', option) == usr_config.get('options', option): option_value = " " + option_value + spaces + "[DEFAULT" + max_spaces + "]" else: option_value = "* " + option_value + spaces + "[DEFAULT: " + default_option_value + default_spaces + "]" message(option, option_value, 0) # all cache names are lower case => bit rate must be lower case too config['video_transcode_bitrate'] = config['video_transcode_bitrate'].lower() # set default values if config['geonames_language'] == '': if config['language'] != '': config['geonames_language'] = config['language'] message("geonames_language option unset", "using language value: " + config['language'], 3) else: config['geonames_language'] = os.getenv('LANG')[:2] message("geonames_language and language options unset", "using system language (" + config['geonames_language'] + ") for geonames_language option", 3) if config['get_geonames_online']: # warn if using demo geonames user if config['geonames_user'] == str(default_config.get('options', 'geonames_user')): message("WARNING!", "You are using the myphotoshare demo geonames user, get and use your own user as soon as possible", 0) # values that have type != string back_level() # @python2 if sys.version_info < (3, ): if config['index_html_path']: config['index_html_path'] = os.path.abspath(config['index_html_path']).decode(sys.getfilesystemencoding()) if config['album_path']: config['album_path'] = os.path.abspath(config['album_path']).decode(sys.getfilesystemencoding()) if config['cache_path']: config['cache_path'] = os.path.abspath(config['cache_path']).decode(sys.getfilesystemencoding()) else: if config['index_html_path']: config['index_html_path'] = os.fsdecode(os.path.abspath(config['index_html_path'])) if config['album_path']: config['album_path'] = os.fsdecode(os.path.abspath(config['album_path'])) if config['cache_path']: config['cache_path'] = os.fsdecode(os.path.abspath(config['cache_path'])) # try to guess value not given guessed_index_dir = False guessed_album_dir = False guessed_cache_dir = False if ( not config['index_html_path'] and not config['album_path'] and not config['cache_path'] ): message("options", "neither index_html_path nor album_path or cache_path have been defined, assuming default positions", 3) # default position for index_html_path is script_path/../web # default position for album path is script_path/../web/albums # default position for cache path is script_path/../web/cache script_path = os.path.dirname(os.path.realpath(sys.argv[0])) config['index_html_path'] = os.path.abspath(os.path.join(script_path, "..", "web")) config['album_path'] = os.path.abspath(os.path.join(config['index_html_path'], "albums")) config['cache_path'] = os.path.abspath(os.path.join(config['index_html_path'], "cache")) guessed_index_dir = True guessed_album_dir = True guessed_cache_dir = True elif ( config['index_html_path'] and not config['album_path'] and not config['cache_path'] ): message("options", "only index_html_path is given, using its subfolder 'albums' for album_path and 'cache' for cache_path", 3) config['album_path'] = os.path.join(config['index_html_path'], "albums") config['cache_path'] = os.path.join(config['index_html_path'], "cache") guessed_album_dir = True guessed_cache_dir = True elif ( not config['index_html_path'] and config['album_path'] and config['cache_path'] and config['album_path'][:config['album_path'] .rfind("/")] == config['cache_path'][:config['cache_path'].rfind("/")] ): guessed_index_dir = True message("options", "only album_path or cache_path has been given, using their common parent folder for index_html_path", 3) config['index_html_path'] = config['album_path'][:config['album_path'].rfind("/")] elif not ( config['index_html_path'] and config['album_path'] and config['cache_path'] ): message("options", "you must define at least some of index_html_path, album_path and cache_path, and correctly; quitting", 0) sys.exit(-97) if guessed_index_dir or guessed_album_dir or guessed_cache_dir: message("options", "guessed value(s):", 2) next_level() if guessed_index_dir: message('index_html_path', config['index_html_path'], 2) if guessed_album_dir: message('album_path', config['album_path'], 2) if guessed_cache_dir: message('cache_path', config['cache_path'], 2) back_level() # the album directory must exist and be readable try: os.stat(config['album_path']) except OSError: message("FATAL ERROR", config['album_path'] + " doesn't exist or unreadable, quitting", 0) sys.exit(-97) # the cache directory must exist and be writable, or we'll try to create it try: os.stat(config['cache_path']) if not os.access(config['cache_path'], os.W_OK): message("FATAL ERROR", config['cache_path'] + " not writable, quitting", 0) sys.exit(-97) except OSError: try: os.mkdir(config['cache_path']) message("directory created", config['cache_path'], 4) os.chmod(config['cache_path'], 0o777) message("permissions set", config['cache_path'], 4) except OSError: message("FATAL ERROR", config['cache_path'] + " inexistent and couldn't be created, quitting", 0) sys.exit(-97) # create the directory where php will put album composite images album_cache_dir = os.path.join(config['cache_path'], config['cache_album_subdir']) try: os.stat(album_cache_dir) except OSError: try: message("creating cache directory for composite images", album_cache_dir, 4) os.mkdir(album_cache_dir) os.chmod(album_cache_dir, 0o777) except OSError: message("FATAL ERROR", config['cache_path'] + " not writable, quitting", 0) sys.exit(-97) # get old options: they are revised in order to decide whether to recreate something json_options_file = os.path.join(config['cache_path'], "options.json") try: with open(json_options_file) as old_options_file: old_options = json.load(old_options_file) except IOError: old_options = config config['recreate_reduced_photos'] = False for option in options_requiring_reduced_images_regeneration: try: if old_options[option] != config[option]: config['recreate_reduced_photos'] = True message("options", "'" + option + "' has changed from previous scanner run, forcing recreation of reduced size images", 3) except KeyError: config['recreate_reduced_photos'] = True message("options", "'" + option + "' wasn't set on previous scanner run, forcing recreation of reduced size images", 3) config['recreate_thumbnails'] = False for option in options_requiring_thumbnails_regeneration: try: if old_options[option] != config[option]: config['recreate_thumbnails'] = True message("options", "'" + option + "' has changed from previous scanner run, forcing recreation of thumbnails", 3) except KeyError: config['recreate_thumbnails'] = True message("options", "'" + option + "' wasn't set on previous scanner run, forcing recreation of thumbnails", 3) config['recreate_json_files'] = False for option in options_requiring_json_regeneration: try: if old_options[option] != config[option]: config['recreate_json_files'] = True message("options", "'" + option + "' has changed from previous scanner run, forcing recreation of json files", 3) break except KeyError: config['recreate_json_files'] = True message("options", "'" + option + "' wasn't set on previous scanner run, forcing recreation of json files", 3) break
def takeTurn(self): """ Take one turn """ message(self.character.name + ' at ' + str(self.character.tile) + ' takes turn.', "AI") #Only take action if we are in a level if self.character.level is None: message(" Not in a level, can't take action.", "AI") return #Only take action if we find the player if self.player is None: for c in self.character.level.characters: if type(c) is Player: self._player = c if self.player is None: message(" No player found, staying put", "AI") return #Only take action if player is not dead. if self.player.state == Character.DEAD: message(" Player is dead, no action needed", "AI") return #TODO medium: read this from the config file via monsterlibrary via #new class variable in Character class RoS = 8 # Range of Sight RoA = 2 # Range of Attack distance = Utilities.distanceBetween(self.character, self.player) #message(' Player ' + self.player.name + ' found at ' + \ # str(self.player.tile) + ' distance: ' + str(distance), "AI") #Only take action if player is within range of sight if distance > RoS: #message(" Player out of range of sight", "AI") return #Attack if player is within range of attack elif distance < RoA: message(" Attacking player", "AI") self.character.attack(self.player) return else: message(" Moving towards player", "AI") self.character.moveTowards(self.player) return