diff --git a/website/api/http.py b/website/api/http.py index d5717c1e..61833fd8 100644 --- a/website/api/http.py +++ b/website/api/http.py @@ -270,12 +270,11 @@ def get_player_data(): if current_user.tile is None: return "", 404 levels = current_user.get_lvls() - config = g.engine.config[current_user.id] capacities = current_user.capacities.get_all() return jsonify( { "levels": levels, - "config": config, + "config": current_user.config, "capacities": capacities, "multipliers": get_current_technology_values(current_user), } diff --git a/website/config/assets.py b/website/config/assets.py index 2dcf4fad..fd2b60fb 100644 --- a/website/config/assets.py +++ b/website/config/assets.py @@ -2,9 +2,13 @@ # TODO: it would be better to store the relevant data in a non-code file. Maybe a JSON +from __future__ import annotations + from datetime import timedelta +from typing import TYPE_CHECKING -from website.database.player import Player +if TYPE_CHECKING: + from website.database.player import Player const_config = { "assets": { @@ -932,17 +936,16 @@ class Config(object): def __init__(self): self.for_player = {} - def update_config_for_user(self, player_id): + def update_config_for_user(self, player: Player): """This function updates the config values according to the players technology level""" # TODO: deprecate this method eventually - self.for_player[player_id] = { + self.for_player[player.id] = { "industry": {}, "carbon_capture": {}, "warehouse_capacities": {}, "transport": {}, } - assets = self.for_player[player_id] - player: Player = Player.query.get(player_id) + assets = self.for_player[player.id] # calculating industry energy consumption and income assets["industry"]["power_consumption"] = ( @@ -986,10 +989,10 @@ def update_config_for_user(self, player_id): player.construction_workers = player_construction_workers_for_level(player.building_technology) player.lab_workers = player_lab_workers_for_level(player.laboratory) - def __getitem__(self, player_id): - if player_id not in self.for_player: - self.update_config_for_user(player_id) - return self.for_player[player_id] + def __getitem__(self, player: Player): + if player.id not in self.for_player: + self.update_config_for_user(player) + return self.for_player[player.id] config = Config() diff --git a/website/database/player.py b/website/database/player.py index 1b149f28..de328196 100644 --- a/website/database/player.py +++ b/website/database/player.py @@ -19,6 +19,13 @@ ) from website.database.messages import Chat, Message, Notification, player_chats from website.database.player_assets import ActiveFacility, OngoingConstruction +from website.technology_effects import ( + package_available_technologies, + package_extraction_facilities, + package_functional_facilities, + package_power_facilities, + package_storage_facilities, +) if TYPE_CHECKING: from website.game_engine import GameEngine @@ -141,43 +148,45 @@ class Player(db.Model, UserMixin): active_facilities = db.relationship("ActiveFacility", backref="player", lazy="dynamic") climate_events = db.relationship("ClimateEventRecovery", backref="player") - _buffered_data_for_power_facilities_page = None - _buffered_data_for_storage_facilities_page = None - _buffered_data_for_extraction_facilities_page = None - _buffered_data_for_functional_facilities_page = None - _buffered_data_for_technologies_page = None - _buffered_data_for_resource_market_page = None - @property def engine(self) -> "GameEngine": return current_app.config["engine"] + @property + def config(self): + return current_app.config["engine"].config[self] + + @property + def socketio_clients(self) -> List[int]: + return current_app.config["engine"].clients[self.id] + @property def current_data(self) -> CircularBufferPlayer: - return current_app.config["engine"].data["players"][self.id]["current_data"] + return current_app.config["engine"].data[type(self).__name__][self.id]["current_data"] @current_data.setter def current_data(self, value): - current_app.config["engine"].data["players"][self.id]["current_data"] = value + current_app.config["engine"].data[type(self).__name__][self.id]["current_data"] = value @property def capacities(self) -> CapacityData: - return current_app.config["engine"].data["players"][self.id]["capacities"] + return current_app.config["engine"].data[type(self).__name__][self.id]["capacities"] @capacities.setter def capacities(self, value): - current_app.config["engine"].data["players"][self.id]["capacities"] = value + current_app.config["engine"].data[type(self).__name__][self.id]["capacities"] = value @property def cumul_emissions(self) -> CumulativeEmissionsData: - return current_app.config["engine"].data["players"][self.id]["cumul_emissions"] + return current_app.config["engine"].data[type(self).__name__][self.id]["cumul_emissions"] @cumul_emissions.setter def cumul_emissions(self, value): - current_app.config["engine"].data["players"][self.id]["cumul_emissions"] = value + current_app.config["engine"].data[type(self).__name__][self.id]["cumul_emissions"] = value @property def is_in_network(self): + """Returns True if the player is in a network""" return self.network_id is not None def change_graph_view(self, view): @@ -574,7 +583,7 @@ def package_constructions(self): ] } | {"display_name": current_app.config["engine"].const_config["assets"][construction.name]["name"]} - | ({"level": construction.level()} if construction.level() >= 0 else {}) + | ({"level": construction.level} if construction.level >= 0 else {}) for construction in self.under_construction } @@ -629,90 +638,138 @@ def get_facility_data(facilities): "extraction_facilities": get_facility_data(engine.extraction_facilities), } + @property + def cached_power_facilities_data(self): + """Cached data for the power facilities page""" + if "cached_power_facilities_data" not in current_app.config["engine"].buffered[type(self).__name__][self.id]: + self.cached_power_facilities_data = package_power_facilities(self) + return current_app.config["engine"].buffered[type(self).__name__][self.id]["cached_power_facilities_data"] + + @cached_power_facilities_data.setter + def cached_power_facilities_data(self, value): + """Sets the cached data for the power facilities page""" + current_app.config["engine"].buffered[type(self).__name__][self.id]["cached_power_facilities_data"] = value + + @cached_power_facilities_data.deleter + def cached_power_facilities_data(self): + """Deletes the cached data for the power facilities page""" + del current_app.config["engine"].buffered[type(self).__name__][self.id]["cached_power_facilities_data"] + + @property + def cached_storage_facilities_data(self): + """Cached data for the storage facilities page""" + if "cached_storage_facilities_data" not in current_app.config["engine"].buffered[type(self).__name__][self.id]: + self.cached_storage_facilities_data = package_storage_facilities(self) + return current_app.config["engine"].buffered[type(self).__name__][self.id]["cached_storage_facilities_data"] + + @cached_storage_facilities_data.setter + def cached_storage_facilities_data(self, value): + """Sets the cached data for the storage facilities page""" + current_app.config["engine"].buffered[type(self).__name__][self.id]["cached_storage_facilities_data"] = value + + @cached_storage_facilities_data.deleter + def cached_storage_facilities_data(self): + """Deletes the cached data for the storage facilities page""" + del current_app.config["engine"].buffered[type(self).__name__][self.id]["cached_storage_facilities_data"] + + @property + def cached_extraction_facility_data(self): + """Cached data for the extraction facilities page""" + if "cached_extraction_facility_data" not in current_app.config["engine"].buffered[type(self).__name__][self.id]: + self.cached_extraction_facility_data = package_extraction_facilities(self) + return current_app.config["engine"].buffered[type(self).__name__][self.id]["cached_extraction_facility_data"] + + @cached_extraction_facility_data.deleter + def cached_extraction_facility_data(self): + """Deletes the cached data for the extraction facilities page""" + del current_app.config["engine"].buffered[type(self).__name__][self.id]["cached_extraction_facility_data"] + + @cached_extraction_facility_data.setter + def cached_extraction_facility_data(self, value): + """Sets the cached data for the extraction facilities page""" + current_app.config["engine"].buffered[type(self).__name__][self.id]["cached_extraction_facility_data"] = value + + @property + def cached_functional_facilities_data(self): + """Cached data for the functional facilities page""" + if ( + "cached_functional_facilities_data" + not in current_app.config["engine"].buffered[type(self).__name__][self.id] + ): + self.cached_functional_facilities_data = package_functional_facilities(self) + return current_app.config["engine"].buffered[type(self).__name__][self.id]["cached_functional_facilities_data"] + + @cached_functional_facilities_data.deleter + def cached_functional_facilities_data(self): + """Deletes the cached data for the functional facilities page""" + del current_app.config["engine"].buffered[type(self).__name__][self.id]["cached_functional_facilities_data"] + + @cached_functional_facilities_data.setter + def cached_functional_facilities_data(self, value): + """Sets the cached data for the functional facilities page""" + current_app.config["engine"].buffered[type(self).__name__][self.id]["cached_functional_facilities_data"] = value + + @property + def cached_technologies_data(self): + """Cached data for the technologies page""" + if "cached_technologies_data" not in current_app.config["engine"].buffered[type(self).__name__][self.id]: + self.cached_technologies_data = package_available_technologies(self) + return current_app.config["engine"].buffered[type(self).__name__][self.id]["cached_technologies_data"] + + @cached_technologies_data.setter + def cached_technologies_data(self, value): + """Sets the cached data for the technologies page""" + current_app.config["engine"].buffered[type(self).__name__][self.id]["cached_technologies_data"] = value + + @cached_technologies_data.deleter + def cached_technologies_data(self): + """Deletes the cached data for the technologies page""" + del current_app.config["engine"].buffered[type(self).__name__][self.id]["cached_technologies_data"] + def invalidate_recompute_and_dispatch_data_for_pages( self, + *, power_facilities=False, storage_facilities=False, extraction_facilities=False, functional_facilities=False, technologies=False, - resource_market=False, + # resource_market=False, ): """ Signal to all instances of clients for this player that there is possibly new data for the specified page. This function will invalidate the data for all corresponding arguments that are set to True. """ if power_facilities: - self._buffered_data_for_power_facilities_page = None + del self.cached_power_facilities_data if storage_facilities: - self._buffered_data_for_storage_facilities_page = None + del self.cached_storage_facilities_data if extraction_facilities: - self._buffered_data_for_extraction_facilities_page = None + del self.cached_extraction_facility_data if functional_facilities: - self._buffered_data_for_functional_facilities_page = None + del self.cached_functional_facilities_data if technologies: - self._buffered_data_for_technologies_page = None - if resource_market: - self._buffered_data_for_resource_market_page = None - engine = current_app.config["engine"] - if engine.clients[self.id]: + del self.cached_technologies_data + # if resource_market: + # self._buffered_data_for_resource_market_page = None + if self.socketio_clients: # or engine.websocket_dict[self.id]: pages_data = {} if power_facilities: - pages_data |= {"power_facilities": self.get_packaged_data_for_power_facilities_page()} + pages_data |= {"power_facilities": self.cached_power_facilities_data} if storage_facilities: - pages_data |= {"storage_facilities": self.get_packaged_data_for_storage_facilities_page()} + pages_data |= {"storage_facilities": self.cached_storage_facilities_data} if extraction_facilities: - pages_data |= {"extraction_facilities": self.get_packaged_data_for_extraction_facilities_page()} + pages_data |= {"extraction_facilities": self.cached_extraction_facility_data} if functional_facilities: - pages_data |= {"functional_facilities": self.get_packaged_data_for_functional_facilities_page()} + pages_data |= {"functional_facilities": self.cached_functional_facilities_data} if technologies: - pages_data |= {"technologies": self.get_packaged_data_for_technologies_page()} + pages_data |= {"technologies": self.cached_technologies_data} # if resource_market: # pages_data |= {"power_facilities": self.get_packaged_data_for_power_facilities_page()} self.emit("update_page_data", pages_data) # TODO: update clients over websocket - def get_packaged_data_for_power_facilities_page(self): - """Get buffered data or recompute""" - from website import technology_effects - - if self._buffered_data_for_power_facilities_page is None: - self._buffered_data_for_power_facilities_page = technology_effects.package_power_facilities(self) - return self._buffered_data_for_power_facilities_page - - def get_packaged_data_for_storage_facilities_page(self): - """Get buffered data or recompute""" - from website import technology_effects - - if self._buffered_data_for_storage_facilities_page is None: - self._buffered_data_for_storage_facilities_page = technology_effects.package_storage_facilities(self) - return self._buffered_data_for_storage_facilities_page - - def get_packaged_data_for_extraction_facilities_page(self): - """Get buffered data or recompute""" - from website import technology_effects - - if self._buffered_data_for_extraction_facilities_page is None: - self._buffered_data_for_extraction_facilities_page = technology_effects.package_extraction_facilities(self) - return self._buffered_data_for_extraction_facilities_page - - def get_packaged_data_for_functional_facilities_page(self): - """Get buffered data or recompute""" - from website import technology_effects - - if self._buffered_data_for_functional_facilities_page is None: - self._buffered_data_for_functional_facilities_page = technology_effects.package_functional_facilities(self) - return self._buffered_data_for_functional_facilities_page - - def get_packaged_data_for_technologies_page(self): - """Get buffered data or recompute""" - from website import technology_effects - - if self._buffered_data_for_technologies_page is None: - self._buffered_data_for_technologies_page = technology_effects.package_available_technologies(self) - return self._buffered_data_for_technologies_page - class Network(db.Model): """class that stores the networks of players""" @@ -723,19 +780,19 @@ class Network(db.Model): @property def current_data(self) -> CircularBufferNetwork: - return current_app.config["engine"].data["networks"][self.id]["current_data"] + return current_app.config["engine"].data[type(self).__name__][self.id]["current_data"] @current_data.setter def current_data(self, value): - current_app.config["engine"].data["networks"][self.id]["current_data"] = value + current_app.config["engine"].data[type(self).__name__][self.id]["current_data"] = value @property def capacities(self) -> CapacityData: - return current_app.config["engine"].data["networks"][self.id]["capacities"] + return current_app.config["engine"].data[type(self).__name__][self.id]["capacities"] @capacities.setter def capacities(self, value): - current_app.config["engine"].data["networks"][self.id]["capacities"] = value + current_app.config["engine"].data[type(self).__name__][self.id]["capacities"] = value class PlayerUnreadMessages(db.Model): diff --git a/website/database/player_assets.py b/website/database/player_assets.py index 7da017a4..8e845de8 100644 --- a/website/database/player_assets.py +++ b/website/database/player_assets.py @@ -1,12 +1,17 @@ """Here are defined the classes for the items stored in the database""" -from typing import List +from typing import TYPE_CHECKING + +from flask import current_app from website import db +if TYPE_CHECKING: + from website.game_engine import GameEngine + class OngoingConstruction(db.Model): - """class that stores the things currently under construction""" + """class that stores the things currently under construction.""" id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(50)) @@ -27,30 +32,6 @@ class OngoingConstruction(db.Model): # can access player directly with .player player_id = db.Column(db.Integer, db.ForeignKey("player.id")) - # A list of OngoingConstruction id's - _prerequisites = None - # The level for this construction if it is a technology or a functional facility, otherwise -1 as special value - _level = None - - def prerequisites(self, recompute=False) -> List[int]: - """Returns a list of the id's of ongoing constructions that this constructions depends on""" - if self._prerequisites is None or recompute: - self._compute_prerequisites_and_level() - return self._prerequisites - - def level(self) -> int: - """ - In the case of functional facilities and technologies, returns the level of this construction. - Otherwise, returns 0. - """ - if self._level is None: - self._compute_prerequisites_and_level() - return self._level - - def reset_prerequisites(self): - """Resets the prerequisites, so that it is recomputed next time prerequisites are accessed""" - self._prerequisites = None - def is_paused(self) -> bool: """Returns True if this construction is paused""" return self.suspension_time is not None @@ -66,46 +47,78 @@ def resume(self): self.start_time += engine.data["total_t"] - self.suspension_time self.suspension_time = None - def _compute_prerequisites_and_level(self): + @property + def level(self) -> int: + """Return the level of the ongoing construction when applicable. + + For functional facilities and technologies, returns the level of this construction. + For other types of constructions, returns None. + """ + if "level" not in current_app.config["engine"].data[type(self).__name__][self.id]: + self.recompute_prerequisites_and_level() + return current_app.config["engine"].data[type(self).__name__][self.id]["level"] + + @level.setter + def level(self, value: int): + """Set the level of the ongoing construction.""" + current_app.config["engine"].data[type(self).__name__][self.id]["level"] = value + + @property + def prerequisites(self) -> list[int]: + """Return a list of the id's of ongoing constructions that this constructions depends on.""" + if "prerequisites" not in current_app.config["engine"].data[type(self).__name__][self.id]: + self.recompute_prerequisites_and_level() + return current_app.config["engine"].data[type(self).__name__][self.id]["prerequisites"] + + @prerequisites.setter + def prerequisites(self, value: list[int]): + """Set the prerequisites of the ongoing construction.""" + current_app.config["engine"].data[type(self).__name__][self.id]["prerequisites"] = value + + def recompute_prerequisites_and_level(self) -> None: + """Recompute the prerequisites and level of an ongoing construction.""" + self.prerequisites, self.level = self._compute_prerequisites_and_level + + def _compute_prerequisites_and_level(self) -> tuple[list[int], int]: + """Compute the prerequisites and level of an ongoing construction.""" from website.database.player import Player + if not TYPE_CHECKING: + from website.database.player_assets import OngoingConstruction + player: Player = Player.query.get(self.player_id) - self._prerequisites = [] + prerequisites = [] + level = None if self.family == "Functional facilities": # For functional facilities, the only prerequisites are ongoing constructions of the same type priority_list = player.read_list("construction_priorities") this_priority_index = priority_list.index(self.id) # Go through all ongoing constructions that are higher up in the priority order - self._level = getattr(player, self.name) + 1 + level = getattr(player, self.name) + 1 for candidate_prerequisite_id in priority_list[:this_priority_index]: # Add them as a prerequisite, if they are of the same type candidate_prerequisite = OngoingConstruction.query.get(candidate_prerequisite_id) if candidate_prerequisite.name == self.name: - self._prerequisites.append(candidate_prerequisite_id) - self._level += 1 - return - if self.family == "Technologies": + prerequisites.append(candidate_prerequisite_id) + level += 1 + elif self.family == "Technologies": # For technologies, const config needs to be checked - from flask import current_app - - from website.game_engine import GameEngine - engine: GameEngine = current_app.config["engine"] const_config = engine.const_config["assets"] requirements = const_config[self.name]["requirements"] priority_list = player.read_list("research_priorities") this_priority_index: int = priority_list.index(self.id) # Compute this constructions level by looking at constructions higher up in the priority list with same name - self._level = getattr(player, self.name) + 1 + level = getattr(player, self.name) + 1 for other_construction_id in priority_list[:this_priority_index]: other_construction: OngoingConstruction = OngoingConstruction.query.get(other_construction_id) if other_construction.name == self.name: - self._level += 1 + level += 1 num_ongoing_researches_of = {} for candidate_prerequisite_id in priority_list[:this_priority_index]: candidate_prerequisite: OngoingConstruction = OngoingConstruction.query.get(candidate_prerequisite_id) if candidate_prerequisite.name == self.name: - self._prerequisites.append(candidate_prerequisite_id) + prerequisites.append(candidate_prerequisite_id) continue if candidate_prerequisite.name in requirements: num_ongoing_researches_of[candidate_prerequisite.name] = ( @@ -117,10 +130,9 @@ def _compute_prerequisites_and_level(self): getattr(player, candidate_prerequisite.name) + num_ongoing_researches_of[candidate_prerequisite.name] ) - if self._level + offset - 1 >= candidate_prerequisite_level: - self._prerequisites.append(candidate_prerequisite_id) - return - self._level = -1 + if level + offset - 1 >= candidate_prerequisite_level: + prerequisites.append(candidate_prerequisite_id) + return prerequisites, level class ActiveFacility(db.Model): diff --git a/website/game_engine.py b/website/game_engine.py index 932064c9..5be416bd 100644 --- a/website/game_engine.py +++ b/website/game_engine.py @@ -149,6 +149,7 @@ def __init__(self, clock_time, in_game_seconds_per_tick, random_seed, start_date self.lock = RLock() self.data = defaultdict(partial(defaultdict, dict)) + self.buffered = defaultdict(partial(defaultdict, dict)) # stores buffered values for mixed_database self.data["random_seed"] = random_seed self.data["total_t"] = 0 # Number of simulated game ticks since server start self.data["start_date"] = start_date or datetime.now() # 0 point of server time diff --git a/website/production_update.py b/website/production_update.py index d7aa5cf4..eb7d3aa9 100644 --- a/website/production_update.py +++ b/website/production_update.py @@ -189,7 +189,7 @@ def calculate_net_import(new_values): def extraction_facility_demand(engine, new_values, player, demand): """Calculate power consumption of extraction facilities""" player_resources = new_values["resources"] - warehouse_caps = engine.config[player.id]["warehouse_capacities"] + warehouse_caps = player.config["warehouse_capacities"] for resource, facility in resource_to_extraction.items(): if player.capacities[facility] is not None: max_warehouse = warehouse_caps[resource] - player_resources[resource] @@ -206,7 +206,6 @@ def extraction_facility_demand(engine, new_values, player, demand): def industry_demand_and_revenues(engine, player, demand, revenues): """calculate power consumption and revenues from industry""" # interpolating seasonal factor on the day - assets = engine.config[player.id] ticks_per_day = 3600 * 24 / engine.in_game_seconds_per_tick real_t = engine.data["total_t"] + engine.data["delta_t"] # this ensures that the year starts at real time midnight day = round(real_t // ticks_per_day) @@ -215,9 +214,9 @@ def industry_demand_and_revenues(engine, player, demand, revenues): seasonal_factor = (sf1 * (ticks_per_day - real_t % ticks_per_day) + sf2 * (real_t % ticks_per_day)) / ticks_per_day intra_day_t = real_t % ticks_per_day intra_day_factor = engine.industry_demand[round(intra_day_t * 1440 / ticks_per_day)] - demand["industry"] = intra_day_factor * seasonal_factor * assets["industry"]["power_consumption"] + demand["industry"] = intra_day_factor * seasonal_factor * player.config["industry"]["power_consumption"] # calculate income of industry per tick - revenues["industry"] = assets["industry"]["income_per_day"] / ticks_per_day + revenues["industry"] = player.config["industry"]["income_per_day"] / ticks_per_day for ud in player.under_construction: # industry demand ramps up during construction if ud.name == "industry": @@ -253,7 +252,7 @@ def construction_demand(player, demand): def shipment_demand(engine, player, demand): """calculate the power consumption for shipments""" - transport = engine.config[player.id]["transport"] + transport = player.config["transport"] for shipment in player.shipments: if shipment.suspension_time is None: demand["transport"] += transport["power_per_kg"] * shipment.quantity @@ -295,7 +294,7 @@ def calculate_demand(engine, new_values, player): climate_event_recovery_cost(player, revenues) if player.carbon_capture > 0: - demand["carbon_capture"] = engine.config[player.id]["carbon_capture"]["power_consumption"] + demand["carbon_capture"] = player.config["carbon_capture"]["power_consumption"] def reset_resource_reservations(): @@ -376,7 +375,7 @@ def calculate_generation_with_market(engine, new_values, market, player): demand_priorities = player.demand_priorities.split(",") # allow a maximum overdraft of the equivalent of the daily income of the industry - max_overdraft = -engine.config[player.id]["industry"]["income_per_day"] + max_overdraft = -player.config["industry"]["income_per_day"] if player.money < max_overdraft: player.notify( "Not Enough Money", "You exceeded your credit limit, you can't buy electricity on the market anymore." @@ -794,10 +793,9 @@ def resources_and_pollution(engine, new_values, player): # Carbon capture CO2 absorption if player.carbon_capture > 0: - assets = engine.config[player.id] - satisfaction = demand["carbon_capture"] / assets["carbon_capture"]["power_consumption"] + satisfaction = demand["carbon_capture"] / player.config["carbon_capture"]["power_consumption"] captured_co2 = ( - assets["carbon_capture"]["absorption"] + player.config["carbon_capture"]["absorption"] * engine.data["current_climate_data"].get_co2() * engine.in_game_seconds_per_tick / 86400 diff --git a/website/technology_effects.py b/website/technology_effects.py index 932ed5bd..a763eb7e 100644 --- a/website/technology_effects.py +++ b/website/technology_effects.py @@ -3,8 +3,10 @@ levels of the player. """ +from __future__ import annotations + import math -from typing import Dict, List +from typing import TYPE_CHECKING, Dict, List import numpy as np from flask import current_app @@ -14,11 +16,12 @@ player_lab_workers_for_level, warehouse_capacity_for_level, ) -from website.database.map import Hex -from website.database.player import Player -from website.game_engine import GameEngine +from website.database.player_assets import ActiveFacility, OngoingConstruction -from .database.player_assets import ActiveFacility, OngoingConstruction +if TYPE_CHECKING: + from website.database.map import Hex + from website.database.player import Player + from website.game_engine import GameEngine # This dictionary describes what multipliers are stored where. # See also the docstring for the individual multiplier functions. @@ -761,22 +764,20 @@ def industry_hourly_revenues_for_level(level): def carbon_capture_power_consumption_for_level(level): if level == 0: return None - else: - return ( - const_config_assets["carbon_capture"]["base_power_consumption"] - * const_config_assets["carbon_capture"]["power_factor"] ** level - ) + return ( + const_config_assets["carbon_capture"]["base_power_consumption"] + * const_config_assets["carbon_capture"]["power_factor"] ** level + ) def carbon_capture_absorption(level): if level == 0: return None - else: - return ( - const_config_assets["carbon_capture"]["base_absorption_per_day"] - * const_config_assets["carbon_capture"]["absorption_factor"] ** level - * engine.data["current_climate_data"].get_co2() # TODO: make this part be a client side computation - / 24 - ) + return ( + const_config_assets["carbon_capture"]["base_absorption_per_day"] + * const_config_assets["carbon_capture"]["absorption_factor"] ** level + * engine.data["current_climate_data"].get_co2() # TODO: make this part be a client side computation + / 24 + ) next_industry_level = next_level(player, "industry") next_laboratory_level = next_level(player, "laboratory") diff --git a/website/utils/__init__.py b/website/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/website/utils/assets.py b/website/utils/assets.py index 2911e91a..3ee36e0b 100644 --- a/website/utils/assets.py +++ b/website/utils/assets.py @@ -132,8 +132,16 @@ def finish_project(construction: OngoingConstruction, skip_notifications=False): if family == "Functional facilities": player.invalidate_recompute_and_dispatch_data_for_pages(functional_facilities=True, technologies=True) if family == "Technologies": - for player in Player.query.all(): - player.invalidate_recompute_and_dispatch_data_for_pages(technologies=True) + player.invalidate_recompute_and_dispatch_data_for_pages( + power_facilities=True, + storage_facilities=True, + extraction_facilities=True, + functional_facilities=True, + technologies=True, + ) + other_players: List[Player] = Player.query.filter(Player.id != player.id).all() + for other_player in other_players: + other_player.invalidate_recompute_and_dispatch_data_for_pages(technologies=True) def deploy_available_workers(player: Player, family: str): @@ -158,7 +166,8 @@ def available_workers() -> int: construction: OngoingConstruction = OngoingConstruction.query.get(construction_id) if not construction.is_paused(): continue - if construction.prerequisites(recompute=True): + construction.recompute_prerequisites_and_level() # force recompute + if construction.prerequisites(): continue construction.resume() insertion_index = None @@ -474,7 +483,7 @@ def cancel_project(player: Player, construction: OngoingConstruction, force=Fals for candidate_dependent_id in priority_list[construction_priority_index + 1 :]: candidate_dependent: OngoingConstruction = OngoingConstruction.query.get(candidate_dependent_id) if construction.id in candidate_dependent.prerequisites(): - dependents.append([candidate_dependent.name, candidate_dependent.level()]) + dependents.append([candidate_dependent.name, candidate_dependent.level]) if dependents: raise GameException("hasDependents", dependents=dependents) @@ -557,7 +566,8 @@ def toggle_pause_project(player: Player, construction: OngoingConstruction): engine.log(f"{player.username} paused the construction {construction.id} {construction.name}") else: # project is currently pause, and should be unpaused - if construction.prerequisites(recompute=True): + construction.recompute_prerequisites_and_level() # force recompute + if construction.prerequisites(): raise GameException("hasUnfinishedPrerequisites") available_workers = ( diff --git a/website/utils/game_engine.py b/website/utils/game_engine.py index f6930314..3656f9ce 100644 --- a/website/utils/game_engine.py +++ b/website/utils/game_engine.py @@ -217,7 +217,7 @@ def climate_event_impact(engine, tile, event): player: Player = tile.player ticks_per_day = 3600 * 24 / engine.in_game_seconds_per_tick recovery_cost = ( - climate_events[event]["cost_fraction"] * engine.config[player.id]["industry"]["income_per_day"] / ticks_per_day + climate_events[event]["cost_fraction"] * player.config["industry"]["income_per_day"] / ticks_per_day ) # [ยค/tick] duration_ticks = math.ceil(climate_events[event]["duration"] / engine.in_game_seconds_per_tick) new_climate_event = ClimateEventRecovery( diff --git a/website/utils/resource_market.py b/website/utils/resource_market.py index 03040f80..2711972b 100644 --- a/website/utils/resource_market.py +++ b/website/utils/resource_market.py @@ -72,9 +72,7 @@ def buy_resource_from_market(player, quantity, sale): dq = player.tile.q - sale.player.tile.q dr = player.tile.r - sale.player.tile.r distance = math.sqrt(2 * (dq**2 + dr**2 + dq * dr)) - shipment_duration = ( - distance * engine.config[player.id]["transport"]["time_per_tile"] / engine.in_game_seconds_per_tick - ) + shipment_duration = distance * player.config["transport"]["time_per_tile"] / engine.in_game_seconds_per_tick shipment_duration = math.ceil(shipment_duration) new_shipment = Shipment( resource=sale.resource, @@ -103,7 +101,7 @@ def buy_resource_from_market(player, quantity, sale): def store_import(player, resource, quantity): """This function is executed when a resource shipment arrives""" engine = current_app.config["engine"] - max_cap = engine.config[player.id]["warehouse_capacities"][resource] + max_cap = player.config["warehouse_capacities"][resource] if getattr(player, resource) + quantity > max_cap: setattr(player, resource, max_cap) # excess resources are stored in the ground diff --git a/website/views.py b/website/views.py index 19ff092d..15a4c585 100644 --- a/website/views.py +++ b/website/views.py @@ -8,13 +8,6 @@ from .database.messages import Chat from .database.player import Player from .database.player_assets import ResourceOnSale -from .technology_effects import ( - package_available_technologies, - package_extraction_facilities, - package_functional_facilities, - package_power_facilities, - package_storage_facilities, -) location_choice_views = Blueprint("location_choice_views", __name__) views = Blueprint("views", __name__) @@ -43,11 +36,7 @@ def set_ctx(): @changelog.before_request def set_ctx_no_login(): """This function is called before every request""" - user = ( - current_user - if current_user.is_authenticated and current_user.tile is not None - else None - ) + user = current_user if current_user.is_authenticated and current_user.tile is not None else None render_template_ctx = partial(render_template, engine=current_app.config["engine"], user=user) g.render_template_ctx = render_template_ctx @@ -100,13 +89,15 @@ def network(): @views.route("/power_facilities") def power_facilities(): - return g.render_template_ctx("assets/power_facilities.jinja", constructions=package_power_facilities(current_user)) + return g.render_template_ctx( + "assets/power_facilities.jinja", constructions=current_user.cached_power_facilities_data + ) @views.route("/storage_facilities") def storage_facilities(): return g.render_template_ctx( - "assets/storage_facilities.jinja", constructions=package_storage_facilities(current_user) + "assets/storage_facilities.jinja", constructions=current_user.cached_storage_facilities_data ) @@ -115,14 +106,14 @@ def technology(): if "Unlock Technologies" not in current_user.achievements: return redirect("/home", code=302) return g.render_template_ctx( - "assets/technologies.jinja", available_technologies=package_available_technologies(current_user) + "assets/technologies.jinja", available_technologies=current_user.cached_available_technologies_data ) @views.route("/functional_facilities") def functional_facilities(): return g.render_template_ctx( - "assets/functional_facilities.jinja", constructions=package_functional_facilities(current_user) + "assets/functional_facilities.jinja", constructions=current_user.cached_functional_facilities_data ) @@ -131,7 +122,7 @@ def extraction_facilities(): if "Unlock Natural Resources" not in current_user.achievements: return redirect("/home", code=302) return g.render_template_ctx( - "assets/extraction_facilities.jinja", constructions=package_extraction_facilities(current_user) + "assets/extraction_facilities.jinja", constructions=current_user.cached_extraction_facilities_data )