1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
|
# ###################################################
# Copyright (C) 2012 The Unknown Horizons Team
# team@unknown-horizons.org
# This file is part of Unknown Horizons.
#
# Unknown Horizons is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the
# Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
# ###################################################
import logging
import horizons.main
from horizons.entities import Entities
from horizons.command import Command
from horizons.command.uioptions import TransferResource
from horizons.util import Point
from horizons.util.worldobject import WorldObject, WorldObjectNotFound
from horizons.scenario import CONDITIONS
from horizons.constants import BUILDINGS, RES
from horizons.world.component.storagecomponent import StorageComponent
class Build(Command):
"""Command class that builds an object."""
def __init__(self, building, x, y, island, rotation = 45, \
ship = None, ownerless=False, settlement=None, tearset=None, data=None, action_set_id=None):
"""Create the command
@param building: building class that is to be built or the id of the building class.
@param x, y: int coordinates where the object is to be built.
@param ship: ship instance
@param island: BuildingOwner instance. Might be Island or World.
@param settlement: settlement worldid or None
@param tearset: set of worldids of objs to tear before building
@param data: data required for building construction
@param action_set_id: use this particular action set, don't choose at random
"""
if hasattr(building, 'id'):
self.building_class = building.id
else:
assert type(building) == int
self.building_class = building
self.ship = None if ship is None else ship.worldid
self.x = int(x)
self.y = int(y)
self.rotation = int(rotation)
self.ownerless = ownerless
self.island = island.worldid
self.settlement = settlement.worldid if settlement is not None else None
self.tearset = set() if not tearset else tearset
self.data = {} if not data else data
self.action_set_id = action_set_id
def __call__(self, issuer=None):
"""Execute the command
@param issuer: the issuer (player, owner of building) of the command
"""
self.log.debug("Build: building type %s at (%s,%s)", self.building_class, self.x, self.y)
island = WorldObject.get_object_by_id(self.island)
# slightly ugly workaround to retrieve world and session instance via pseudo-singleton
session = island.session
# check once agaion. needed for MP because of the execution delay.
buildable_class = Entities.buildings[self.building_class]
build_position = buildable_class.check_build(session, Point(self.x, self.y), \
rotation=self.rotation,\
check_settlement=issuer is not None, \
ship=WorldObject.get_object_by_id(self.ship) if self.ship is not None else None,
issuer=issuer)
# it's possible that the build check requires different actions now,
# so update our data
self.x, self.y = build_position.position.origin.to_tuple()
self.rotation = build_position.rotation
self.tearset = build_position.tearset
if build_position.buildable and issuer:
# building seems to buildable, check res too now
res_sources = [ None if self.ship is None else WorldObject.get_object_by_id(self.ship),
None if self.settlement is None else WorldObject.get_object_by_id(self.settlement) ]
(build_position.buildable, missing_res) = \
self.check_resources({}, buildable_class.costs, issuer, res_sources)
if not build_position.buildable:
self.log.debug("Build aborted. Seems like circumstances changed during EXECUTIONDELAY.")
# TODO: maybe show message to user
return
# collect data before objs are torn
# required by e.g. the mines to find out about the status of the resource deposit
if hasattr(Entities.buildings[self.building_class], "get_prebuild_data"):
self.data.update( \
Entities.buildings[self.building_class].get_prebuild_data(session, Point(self.x, self.y)) \
)
for worldid in sorted(self.tearset): # make sure iteration is the same order everywhere
try:
obj = WorldObject.get_object_by_id(worldid)
Tear(obj)(issuer=None) # execute right now, not via manager
except WorldObjectNotFound: # obj might have been removed already
pass
building = Entities.buildings[self.building_class]( \
session=session, \
x=self.x, y=self.y, \
rotation=self.rotation, owner=issuer if not self.ownerless else None, \
island=island, \
instance=None, \
action_set_id=self.action_set_id, \
**self.data
)
building.initialize(**self.data)
# initialize must be called immediately after the construction
# the building is not usable before this call
island.add_building(building, issuer)
if self.settlement is not None:
secondary_resource_source = WorldObject.get_object_by_id(self.settlement)
elif self.ship is not None:
secondary_resource_source = WorldObject.get_object_by_id(self.ship)
elif island is not None:
secondary_resource_source = island.get_settlement(Point(self.x, self.y))
if issuer: # issuer is None if it's a global game command, e.g. on world setup
for (resource, value) in building.costs.iteritems():
# remove from issuer, and remove rest from secondary source (settlement or ship)
first_source_remnant = issuer.get_component(StorageComponent).inventory.alter(resource, -value)
if first_source_remnant != 0 and secondary_resource_source is not None:
second_source_remnant = secondary_resource_source.get_component(StorageComponent).inventory.alter(resource, first_source_remnant)
assert second_source_remnant == 0
else: # first source must have covered everything
assert first_source_remnant == 0
# building is now officially built and existent
building.start()
# unload the remaining resources on the human player ship if we just founded a new settlement
from horizons.world.player import HumanPlayer
if building.id == BUILDINGS.WAREHOUSE_CLASS and isinstance(building.owner, HumanPlayer) and horizons.main.fife.get_uh_setting("AutoUnload"):
ship = WorldObject.get_object_by_id(self.ship)
for res, amount in [(res, amount) for res, amount in ship.get_component(StorageComponent).inventory]: # copy the inventory first because otherwise we would modify it while iterating
amount = min(amount, building.settlement.get_component(StorageComponent).inventory.get_free_space_for(res))
# execute directly, we are already in a command
TransferResource(amount, res, ship, building.settlement)(issuer=issuer)
# NOTE: conditions are not MP-safe! no problem as long as there are no MP-scenarios
session.scenario_eventhandler.schedule_check(CONDITIONS.building_num_of_type_greater)
return building
@staticmethod
def check_resources(neededResources, costs, issuer, res_sources):
"""Check if there are enough resources available to cover the costs
@param neededResources: awkward dict from BuildingTool.preview_build, use {} everywhere else
@param costs: building costs (as in buildingclass.costs)
@param issuer: player that builds the building
@param res_sources: list of objects with inventory attribute. None values are discarded.
@return tuple(bool, missing_resource), True means buildable"""
for resource in costs:
neededResources[resource] = neededResources.get(resource, 0) + costs[resource]
for resource in neededResources:
# check player, ship and settlement inventory
available_res = 0
# player
available_res += issuer.get_component(StorageComponent).inventory[resource] if resource == RES.GOLD_ID else 0
# ship or settlement
for res_source in res_sources:
if res_source is not None:
available_res += res_source.get_component(StorageComponent).inventory[resource]
if available_res < neededResources[resource]:
return (False, resource)
return (True, None)
Command.allow_network(Build)
Command.allow_network(set)
class Tear(Command):
"""Command class that tears an object."""
def __init__(self, building):
"""Create the command
@param building: building that is to be teared.
"""
self.building = building.worldid
def __call__(self, issuer):
"""Execute the command
@param issuer: the issuer of the command
"""
try:
building = WorldObject.get_object_by_id(self.building)
except WorldObjectNotFound:
self.log.debug("Tear: building %s already gone, not tearing it again.", self.building)
return # invalid command, possibly caused by mp delay
if building is None or building.fife_instance is None:
self.log.warning("Tear: attempting to tear down a building that shouldn't exist %s", building)
print "Tear: attempting to tear down a building that shouldn't exist %s" % building
else:
self.log.debug("Tear: tearing down %s", building)
building.remove()
Command.allow_network(Tear)
|