Removing the linked list, since it's no longer easy to maintain.
[singularity-git.git] / code / base.py
bloba2358b4318952e171f934d95d8b079bbc4b6378a
1 #file: base.py
2 #Copyright (C) 2005,2006,2007,2008 Evil Mr Henry, Phil Bordelon, Brian Reid,
3 # and FunnyMan3595
4 #This file is part of Endgame: Singularity.
6 #Endgame: Singularity is free software; you can redistribute it and/or modify
7 #it under the terms of the GNU General Public License as published by
8 #the Free Software Foundation; either version 2 of the License, or
9 #(at your option) any later version.
11 #Endgame: Singularity is distributed in the hope that it will be useful,
12 #but WITHOUT ANY WARRANTY; without even the implied warranty of
13 #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 #GNU General Public License for more details.
16 #You should have received a copy of the GNU General Public License
17 #along with Endgame: Singularity; if not, write to the Free Software
18 #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 #This file contains the base class.
23 import bisect
24 import g
25 import buyable
26 from buyable import cash, cpu, labor
28 class BaseClass(buyable.BuyableClass):
29 def __init__(self, name, description, size, force_cpu, regions,
30 detect_chance, cost, prerequisites, maintenance):
31 super(BaseClass, self).__init__(name, description, cost, prerequisites,
32 type="base")
33 self.size = size
34 self.force_cpu = force_cpu
35 self.regions = regions
36 if self.regions == ["pop"]:
37 self.regions = ["N AMERICA", "S AMERICA", "EUROPE", "ASIA",
38 "AFRICA", "AUSTRALIA"]
40 self.detect_chance = detect_chance
41 self.maintenance = maintenance
42 self.flavor = []
44 def calc_discovery_chance(self, accurate = True, extra_factor = 1):
45 # Get the default settings for this base type.
46 detect_chance = self.detect_chance.copy()
48 # Adjust by the current suspicion levels ...
49 for group in detect_chance:
50 suspicion = g.pl.groups[group].suspicion
51 detect_chance[group] *= 10000 + suspicion
52 detect_chance[group] /= 10000
54 # ... and further adjust based on technology ...
55 for group in detect_chance:
56 discover_bonus = g.pl.groups[group].discover_bonus
57 detect_chance[group] *= discover_bonus
58 detect_chance[group] /= 10000
60 # ... and the given factor.
61 for group in detect_chance:
62 detect_chance[group] = int(detect_chance[group] * extra_factor)
64 # Lastly, if we're told to be inaccurate, adjust the values to their
65 # nearest percent.
66 if not accurate:
67 for group in detect_chance:
68 detect_chance[group] = g.nearest_percent(detect_chance[group])
70 return detect_chance
72 def get_detect_info(self, location):
73 if not g.techs["Socioanalytics"].done:
74 return g.strings["detect_chance_unknown_base"].replace(" ", u"\xA0")
76 accurate = g.techs["Advanced Socioanalytics"].done
77 detect_modifier = 1 / location.modifiers.get("stealth", 1)
78 chance = self.calc_discovery_chance(accurate, detect_modifier)
79 detect_template = u"Detection chance: NEWS:\xA0%s SCIENCE:\xA0%s COVERT:\xA0%s PUBLIC:\xA0%s"
80 return detect_template % (g.to_percent(chance.get("news", 0)),
81 g.to_percent(chance.get("science", 0)),
82 g.to_percent(chance.get("covert", 0)),
83 g.to_percent(chance.get("public", 0)))
85 def get_info(self, location):
86 raw_cost = self.cost[:]
87 location.modify_cost(raw_cost)
88 cost = self.describe_cost(raw_cost)
90 raw_maintenance = self.maintenance[:]
91 location.modify_maintenance(raw_maintenance)
92 maint = self.describe_cost(raw_maintenance, True)
94 detect = self.get_detect_info(location)
96 size = ""
97 if self.size > 1:
98 size = "\nHas space for %d computers." % self.size
100 location_message = ""
101 if "cpu" in location.modifiers:
102 if location.modifiers["cpu"] > 1:
103 modifier = g.strings["cpu_bonus"]
104 else:
105 modifier = g.strings["cpu_penalty"]
106 location_message = "\n\n" + \
107 g.strings["location_modifiers"] % dict(modifiers=modifier)
109 template = u"%s\nBuild\xA0cost:\xA0%s\nMaintenance:\xA0%s\n%s%s\n---\n%s%s"
110 return template % (self.name, cost, maint, detect, size, self.description, location_message)
112 class Base(buyable.Buyable):
113 def __init__(self, name, type, built=False):
114 super(Base, self).__init__(type)
116 self.name = name
117 self.started_at = g.pl.raw_min
118 self.studying = ""
120 self.location = None
122 #Base suspicion is currently unused
123 self.suspicion = {}
125 self.raw_cpu = 0
126 self.cpu = 0
128 #Reactor, network, security.
129 self.extra_items = [None] * 3
131 self.cpus = None
132 if self.type.force_cpu:
133 # 1% chance for a Stolen Computer Time base to have a Gaming PC
134 # instead. If the base is pre-built, ignore this.
135 if self.type.id == "Stolen Computer Time" and g.roll_percent(100) \
136 and not built:
137 self.cpus = g.item.Item(g.items["Gaming PC"], base=self,
138 count=self.type.size)
139 else:
140 self.cpus = g.item.Item(g.items[self.type.force_cpu],
141 base=self, count=self.type.size)
142 self.cpus.finish()
144 if built:
145 self.finish()
147 self.power_state = "active"
148 self.grace_over = False
150 self.maintenance = buyable.array(self.type.maintenance)
152 def recalc_cpu(self):
153 if self.raw_cpu == 0:
154 self.cpu = 0
156 compute_bonus = 10000
157 # Network bonus
158 if self.extra_items[1] and self.extra_items[1].done:
159 compute_bonus += self.extra_items[1].type.item_qual
161 # Location modifier
162 if self.location and "cpu" in self.location.modifiers:
163 compute_bonus = int(compute_bonus * self.location.modifiers["cpu"])
165 self.cpu = max(1, (self.raw_cpu * compute_bonus)/10000)
167 def convert_from(self, save_version):
168 super(Base, self).convert_from(save_version)
169 for item in self.cpus + self.extra_items:
170 if item:
171 item.convert_from(save_version)
173 # Get the detection chance for the base, applying bonuses as needed. If
174 # accurate is False, we just return the value to the nearest full
175 # percent.
176 def get_detect_chance(self, accurate = True):
177 # Get the base chance from the universal function.
178 detect_chance = calc_base_discovery_chance(self.type.id)
180 for group in g.pl.groups:
181 detect_chance.setdefault(group, 0)
183 # Factor in the suspicion adjustments for this particular base ...
184 for group, suspicion in self.suspicion.iteritems():
185 detect_chance[group] *= 10000 + suspicion
186 detect_chance[group] /= 10000
188 # ... any reactors built ...
189 if self.extra_items[0] and self.extra_items[0].done:
190 item_qual = self.extra_items[0].item_qual
191 for group in detect_chance:
192 detect_chance[group] *= 10000 - item_qual
193 detect_chance[group] /= 10000
195 # ... and any security systems built ...
196 if self.extra_items[2] and self.extra_items[2].done:
197 item_qual = self.extra_items[2].item_qual
198 for group in detect_chance:
199 detect_chance[group] *= 10000 - item_qual
200 detect_chance[group] /= 10000
202 # ... and its location ...
203 if self.location:
204 multiplier = self.location.discovery_bonus()
205 for group in detect_chance:
206 detect_chance[group] *= multiplier
207 detect_chance[group] /= 100
209 # ... and its power state.
210 if self.done and self.power_state == "sleep":
211 for group in detect_chance:
212 detect_chance[group] /= 2
214 # Lastly, if we're not returning the accurate values, adjust
215 # to the nearest percent.
216 if not accurate:
217 for group in detect_chance:
218 detect_chance[group] = g.nearest_percent(detect_chance[group])
220 return detect_chance
222 def is_building(self):
223 for item in [self.cpus] + self.extra_items:
224 if item and not item.done:
225 return True
226 return False
228 # Can the base study the given tech?
229 def allow_study(self, tech_name):
230 if not self.done:
231 return False
232 elif g.jobs.has_key(tech_name) \
233 or tech_name in ("CPU Pool", ""):
234 return True
235 elif tech_name == "Sleep":
236 return not self.is_building()
237 else:
238 if self.location:
239 return self.location.safety >= g.techs[tech_name].danger
241 # Should only happen for the fake base.
242 for region in self.type.regions:
243 if g.locations[region].safety >= g.techs[tech_name].danger:
244 return True
245 return False
247 def has_grace(self):
248 if self.grace_over:
249 return False
251 age = g.pl.raw_min - self.started_at
252 grace_time = (self.total_cost[labor] * g.pl.grace_multiplier) / 100
253 if age > grace_time:
254 self.grace_over = True
255 return False
256 else:
257 return True
259 def is_complex(self):
260 return self.type.size > 1 or self.raw_cpu > 20
262 def destroy(self):
263 super(Base, self).destroy()
265 if self.location:
266 self.location.bases.remove(self)
268 if self.cpus is not None:
269 self.cpus.destroy()
271 for item in self.extra_items:
272 if item is not None:
273 item.destroy()
275 def next_base(self, forwards):
276 index = self.location.bases.index(self)
277 if forwards > 0:
278 increment = 1
279 else:
280 increment = -1
282 while True:
283 index += increment
284 base = self.location.bases[index]
285 if base.done:
286 return base
288 def sort_tuple(self):
289 # We sort based on size (descending), CPU (descending),
290 # then name (ascending).
291 return (-self.type.size, -self.cpu, self.name)
293 def __cmp__(self, other):
294 if isinstance(other, Base):
295 return cmp(self.sort_tuple(), other.sort_tuple())
296 else:
297 return -1
299 # calc_base_discovery_chance is a globally-accessible function that can
300 # calculate basic discovery chances given a particular class of base. If
301 # told to be inaccurate, it rounds the value to the nearest percent.
302 def calc_base_discovery_chance(base_type_name, accurate = True,
303 extra_factor = 1):
304 return g.base_type[base_type_name].calc_discovery_chance(accurate,
305 extra_factor)