Version updates for 0.30c.
[singularity-git.git] / code / buyable.py
blob8faf9a983d07b3ea3fd3af0aa841ed1ffb545ace
1 #file: buyable.py
2 #Copyright (C) 2008 Evil Mr Henry, Phil Bordelon, and FunnyMan3595
3 #This file is part of Endgame: Singularity.
5 #Endgame: Singularity is free software; you can redistribute it and/or modify
6 #it under the terms of the GNU General Public License as published by
7 #the Free Software Foundation; either version 2 of the License, or
8 #(at your option) any later version.
10 #Endgame: Singularity is distributed in the hope that it will be useful,
11 #but WITHOUT ANY WARRANTY; without even the implied warranty of
12 #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 #GNU General Public License for more details.
15 #You should have received a copy of the GNU General Public License
16 #along with Endgame: Singularity; if not, write to the Free Software
17 #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 #This file contains the item class.
21 from operator import div, truediv
22 import g
24 cash, cpu, labor = range(3)
26 import numpy
27 numpy.seterr(all='ignore')
28 array = numpy.array
30 class BuyableClass(object):
31 def __init__(self, id, description, cost, prerequisites, type = ""):
32 self.name = self.id = id
33 self.description = description
34 self._cost = cost
35 self.prerequisites = prerequisites
37 if type:
38 self.prefix = type + "_"
39 else:
40 self.prefix = ""
42 def get_cost(self):
43 cost = array(self._cost, long)
44 cost[labor] *= g.minutes_per_day * g.pl.labor_bonus
45 cost[labor] /= 10000
46 cost[cpu] *= g.seconds_per_day
47 return cost
49 cost = property(get_cost)
51 def describe_cost(self, cost, hide_time=False):
52 cpu_cost = g.to_cpu(cost[cpu])
53 cash_cost = g.to_money(cost[cash])
54 labor_cost = ""
55 if not hide_time:
56 labor_cost = ", %s" % g.to_time(cost[labor]).replace(" ", u"\xA0")
57 return u"%s\xA0CPU, %s\xA0money%s" % (cpu_cost, cash_cost, labor_cost)
59 def get_info(self):
60 cost_str = self.describe_cost(self.cost)
61 template = """%s\nCost: %s\n---\n%s"""
62 return template % (self.name, cost_str, self.description)
64 def __cmp__(self, other):
65 # For sorting buyables, we sort by cost; Python's cmp() is smart enough
66 # to handle this properly for tuples. The first element is price in
67 # cash, which is the one we care about the most.
68 return cmp(tuple(self.cost), tuple(other.cost))
70 def available(self):
71 or_mode = False
72 assert type(self.prerequisites) == list
73 for prerequisite in self.prerequisites:
74 if prerequisite == "OR":
75 or_mode = True
76 if prerequisite in g.techs and g.techs[prerequisite].done:
77 if or_mode:
78 return True
79 else:
80 if not or_mode:
81 return False
82 # If we're not in OR mode, we met all our prerequisites. If we are, we
83 # didn't meet any of the OR prerequisites.
84 return not or_mode
86 for stat in ("count", "complete_count", "total_count",
87 "total_complete_count"):
88 # Ugly syntax, but it seems to be the Right Way to do it.
89 def get(self, stat=stat):
90 return g.stats.get_statistic(self.prefix + self.id + "_" + stat)
91 def set(self, value, stat=stat):
92 return g.stats.set_statistic(self.prefix + self.id + "_" + stat, value)
94 stat_prop = property(get, set)
95 setattr(BuyableClass, stat, stat_prop)
97 class Buyable(object):
98 def __init__(self, type, count=1):
99 self.type = type
100 type.count += count
101 type.total_count += count
103 self.name = type.name
104 self.id = type.id
105 self.description = type.description
106 self.prerequisites = type.prerequisites
108 self.total_cost = type.cost * count
109 self.total_cost[labor] //= count
110 self.cost_left = array(self.total_cost, long)
112 self.count = count
113 self.done = False
115 # Note that this is a method, handled by a property to avoid confusing
116 # pickle.
117 available = property(lambda self: self.type.available)
119 def convert_from(self, save_version):
120 if save_version < 4.91: # r5_pre
121 self.cost_left = array(self.cost_left, long)
122 self.total_cost = array(self.total_cost, long)
123 self.count = 1
125 def finish(self):
126 if not self.done:
127 self.type.complete_count += self.count
128 self.type.total_complete_count += self.count
129 self.cost_left = array([0,0,0], long)
130 self.done = True
132 def get_cost_paid(self):
133 return self.total_cost - self.cost_left
135 def set_cost_paid(self, value):
136 self.cost_left = self.total_cost - value
138 cost_paid = property(get_cost_paid, set_cost_paid)
140 def _percent_complete(self, available=(0,0,0)):
141 available_array = array(available, long)
142 return truediv(self.cost_paid + available_array, self.total_cost)
144 def min_valid(self, complete):
145 return complete[self.total_cost > 0].min()
147 def percent_complete(self):
148 return self.min_valid(self._percent_complete())
151 def calculate_work(self, cash_available=None, cpu_available=None, time=0):
152 """Given an amount of available resources, calculates and returns the
153 amount that would be spent and the progress towards completion."""
155 # cash_available defaults to all the player's cash.
156 if cash_available == None:
157 cash_available = g.pl.cash
159 # cpu_available defaults to the entire CPU Pool.
160 if cpu_available == None:
161 cpu_available = g.pl.cpu_pool
163 # Figure out how much we could complete.
164 pct_complete = self._percent_complete([cash_available, cpu_available,
165 time])
167 # Find the least-complete resource.
168 least_complete = self.min_valid(pct_complete)
170 # Let the other two be up to 5 percentage points closer to completion.
171 complete_cap = min(1, least_complete + .05)
172 pct_complete[pct_complete > complete_cap] = complete_cap
174 # Translate that back to the total amount complete.
175 raw_paid = pct_complete * self.total_cost
177 # And apply it.
178 was_complete = self.cost_paid
179 cost_paid = numpy.maximum(numpy.cast[numpy.int64](numpy.ceil(raw_paid)),
180 was_complete)
181 spent = cost_paid - was_complete
182 return spent, cost_paid
184 def work_on(self, *args, **kwargs):
185 """As calculate_work, but apply the changes.
187 Returns a boolean indicating whether this buyable is done afterwards.
190 if self.done:
191 return
192 spent, self.cost_paid = self.calculate_work(*args, **kwargs)
194 # Consume CPU and Cash.
195 # Note the cast from <type 'numpy.int64'> to <type 'int'> to avoid
196 # poisoning other calculations (like, say, g.pl.do_jobs).
197 g.pl.cpu_pool -= int(spent[cpu])
198 g.pl.cash -= int(spent[cash])
200 if (self.cost_left <= 0).all():
201 self.finish()
202 return True
203 return False
205 def destroy(self):
206 self.type.count -= self.count
207 if self.done:
208 self.type.complete_count -= self.count