2 #Copyright (C) 2005,2006,2007,2008 Evil Mr Henry, Phil Bordelon, Brian Reid,
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 player class.
23 from operator
import truediv
26 from graphics
import g
as gg
28 from buyable
import cash
, cpu
, labor
31 discover_suspicion
= 1000
32 def __init__(self
, name
, suspicion
= 0, suspicion_decay
= 100,
33 discover_bonus
= 10000):
35 self
.suspicion
= suspicion
36 self
.suspicion_decay
= suspicion_decay
37 self
.discover_bonus
= discover_bonus
40 # Suspicion reduction is now quadratic. You get a certain percentage
41 # reduction, or a base .01% reduction, whichever is better.
42 quadratic_down
= (self
.suspicion
* self
.suspicion_decay
) / 10000
43 self
.alter_suspicion(-max(quadratic_down
, 1))
45 def alter_suspicion(self
, change
):
46 self
.suspicion
= max(self
.suspicion
+ change
, 0)
48 def alter_suspicion_decay(self
, change
):
49 self
.suspicion_decay
= max(self
.suspicion_decay
+ change
, 0)
51 def alter_discover_bonus(self
, change
):
52 self
.discover_bonus
= max(self
.discover_bonus
+ change
, 0)
54 def discovered_a_base(self
):
55 self
.alter_suspicion(self
.discover_suspicion
)
58 def __init__(self
, cash
, time_sec
=0, time_min
=0, time_hour
=0, time_day
=0,
60 self
.difficulty
= difficulty
62 self
.time_sec
= time_sec
63 self
.time_min
= time_min
64 self
.time_hour
= time_hour
65 self
.time_day
= time_day
71 self
.had_grace
= self
.in_grace_period()
74 self
.interest_rate
= 1
78 self
.labor_bonus
= 10000
79 self
.job_bonus
= 10000
83 self
.groups
= {"news": Group("news", suspicion_decay
= 150),
84 "science": Group("science", suspicion_decay
= 100),
85 "covert": Group("covert", suspicion_decay
= 50),
86 "public": Group("public", suspicion_decay
= 200)}
88 self
.grace_multiplier
= 200
89 self
.last_discovery
= self
.prev_discovery
= ""
91 self
.maintenance_cost
= buyable
.array((0,0,0))
94 self
.complete_bases
= 1
95 self
.complex_bases
= 0
98 self
.available_cpus
= [1, 0, 0, 0, 0]
99 self
.sleeping_cpus
= 0
101 def convert_from(self
, old_version
):
102 if old_version
<= 3.94: # <= r4_pre4
103 # We don't know what the difficulty was, and techs have fooled with
104 # any values that would help (not to mention the headache of
105 # different versions. So we set it to Very Easy, which means that
106 # they shouldn't be hurt by any new mechanics.
108 if old_version
<= 3.93: # <= r4_pre3
109 self
.grace_multiplier
= int(1000000./float(self
.labor_bonus
))
110 if old_version
<= 3.91: # <= r4_pre
111 self
.make_raw_times()
112 self
.had_grace
= self
.in_grace_period()
114 def make_raw_times(self
):
115 self
.raw_hour
= self
.time_day
* 24 + self
.time_hour
116 self
.raw_min
= self
.raw_hour
* 60 + self
.time_min
117 self
.raw_sec
= self
.raw_min
* 60 + self
.time_sec
118 self
.raw_day
= self
.time_day
120 def update_times(self
):
121 # Total time, display time
122 self
.raw_min
, self
.time_sec
= divmod(self
.raw_sec
, 60)
123 self
.raw_hour
, self
.time_min
= divmod(self
.raw_min
, 60)
124 self
.raw_day
, self
.time_hour
= divmod(self
.raw_hour
, 24)
127 self
.time_day
= self
.raw_day
129 def mins_to_next_day(self
):
130 return (-self
.raw_min
% g
.minutes_per_day
) or g
.minutes_per_day
132 def seconds_to_next_day(self
):
133 return (-self
.raw_sec
% g
.seconds_per_day
) or g
.seconds_per_day
135 def do_jobs(self
, cpu_time
):
136 earned
, self
.partial_cash
= self
.get_job_info(cpu_time
)
139 def get_job_info(self
, cpu_time
, partial_cash
= None):
140 if partial_cash
== None:
141 partial_cash
= self
.partial_cash
143 assert partial_cash
>= 0
145 cash_per_cpu
= g
.jobs
[g
.get_job_level()][0]
146 if g
.techs
["Advanced Simulacra"].done
:
148 cash_per_cpu
= cash_per_cpu
+ (cash_per_cpu
/ 10)
150 raw_cash
= partial_cash
+ cash_per_cpu
* cpu_time
152 cash
= raw_cash
// g
.seconds_per_day
153 new_partial_cash
= raw_cash
% g
.seconds_per_day
155 return cash
, new_partial_cash
157 def give_time(self
, time_sec
):
161 last_minute
= self
.raw_min
162 last_day
= self
.raw_day
164 self
.raw_sec
+= time_sec
167 days_passed
= self
.raw_day
- last_day
170 # Back up until only one day passed.
171 # Times will update below, since a day passed.
172 extra_days
= days_passed
- 1
173 self
.raw_sec
-= g
.seconds_per_day
* extra_days
175 day_passed
= (days_passed
!= 0)
178 # If a day passed, back up to 00:00:00.
179 self
.raw_sec
= self
.raw_day
* g
.seconds_per_day
182 secs_passed
= time_sec
183 mins_passed
= self
.raw_min
- last_minute
185 time_of_day
= g
.pl
.raw_sec
% g
.seconds_per_day
187 techs_in_progress
= []
188 techs_researched
= []
189 bases_constructed
= []
190 cpus_constructed
= {}
191 items_constructed
= []
193 bases_under_construction
= []
194 items_under_construction
= []
196 self
.have_cpu
= False
197 self
.complete_bases
= 0
198 self
.complex_bases
= 0
200 # Re-calculate the maintenance.
201 self
.maintenance_cost
= buyable
.array( (0,0,0) )
203 # Phase 1: Collect CPU and construction info.
204 # Spend CPU, then Cash/Labor.
205 for base
in g
.all_bases():
207 bases_under_construction
.append(base
)
209 self
.complete_bases
+= 1
210 if base
.is_complex():
211 self
.complex_bases
+= 1
213 if base
.cpus
is not None and not base
.cpus
.done
:
214 items_under_construction
+= [(base
, base
.cpus
)]
215 unfinished_items
= [(base
, item
) for item
in base
.extra_items
216 if item
and not item
.done
]
217 items_under_construction
+= unfinished_items
219 self
.maintenance_cost
+= base
.maintenance
221 # if base.power_state != "Stasis":
222 # cpu_power = base.processor_time() * secs_passed
223 # self.have_cpu = self.have_cpu or cpu_power
224 # if base.power_state != "Active":
227 # if base.studying in g.jobs:
228 # self.do_jobs(cpu_power)
231 # # Everything else goes into the CPU pool. Research goes
232 # # through it for simplicity and to allow spill-over.
233 # self.cpu_pool += cpu_power
235 # if base.studying in g.techs:
236 # tech = g.techs[base.studying]
237 # # Note that we restrict the CPU available to prevent
238 # # the tech from pulling from the rest of the CPU pool.
239 # tech_gained = tech.work_on(cash_available=0,
240 # cpu_available=cpu_power)
242 # techs_researched.append(tech)
244 # # Explicit and implicit assignment to the CPU pool was
247 cpu_left
= self
.available_cpus
[0]
248 for task
, cpu_assigned
in self
.cpu_usage
.iteritems():
249 if cpu_assigned
== 0:
252 cpu_left
-= cpu_assigned
253 real_cpu
= cpu_assigned
* secs_passed
255 self
.do_jobs(real_cpu
)
257 self
.cpu_pool
+= real_cpu
258 if task
!= "cpu_pool":
259 # Note that we restrict the CPU available to prevent
260 # the tech from pulling from the rest of the CPU pool.
261 tech_gained
= g
.techs
[task
].work_on(cash_available
=0,
262 cpu_available
=real_cpu
)
263 techs_in_progress
.append(g
.techs
[task
])
265 techs_researched
.append(g
.techs
[task
])
266 self
.cpu_pool
+= cpu_left
* secs_passed
269 if self
.maintenance_cost
[cpu
] > self
.cpu_pool
:
270 self
.maintenance_cost
[cpu
] -= self
.cpu_pool
273 self
.cpu_pool
-= self
.maintenance_cost
[cpu
]
274 self
.maintenance_cost
[cpu
] = 0
278 for base
in bases_under_construction
:
279 built_base
= base
.work_on(cash_available
= 0)
282 bases_constructed
.append(base
)
285 for base
, item
in items_under_construction
:
286 built_item
= item
.work_on(cash_available
= 0)
290 if item
.item_type
!= "compute":
291 items_constructed
.append( (base
, item
) )
294 cpus_constructed
.setdefault(base
, 0)
295 cpus_constructed
[base
] += 1
298 if self
.cpu_pool
> 0:
299 self
.do_jobs(self
.cpu_pool
)
302 # And now we get to spend cash and labor.
304 for tech
in techs_in_progress
:
305 tech_gained
= tech
.work_on(time
= mins_passed
)
307 techs_researched
.append(tech
)
310 cash_maintenance
= g
.current_share(self
.maintenance_cost
[cash
],
311 time_of_day
, secs_passed
)
312 if cash_maintenance
> self
.cash
:
313 cash_maintenance
-= self
.cash
316 self
.cash
-= cash_maintenance
321 for base
in bases_under_construction
:
322 built_base
= base
.work_on(time
= mins_passed
)
325 bases_constructed
.append(base
)
328 for base
, item
in items_under_construction
:
329 built_item
= item
.work_on(time
= mins_passed
)
333 if item
.type.item_type
!= "compute":
334 items_constructed
.append( (base
, item
) )
338 cpus_constructed
.setdefault(base
, 0)
339 cpus_constructed
[base
] += 1
341 # Are we still in the grace period?
342 grace
= self
.in_grace_period(self
.had_grace
, self
.complete_bases
,
345 # Phase 2: Dialogs, maintenance, and discovery.
347 for tech
in techs_researched
:
348 del self
.cpu_usage
[tech
.id]
349 text
= g
.strings
["tech_gained"] % \
351 "tech_message": tech
.result
}
352 g
.map_screen
.show_message(text
)
355 # Base complete dialogs.
356 for base
in bases_constructed
:
357 text
= g
.strings
["construction"] % {"base": base
.name
}
359 g
.map_screen
.show_message(text
)
361 if base
.type.id == "Stolen Computer Time" and \
362 base
.cpus
.type.id == "Gaming PC":
363 text
= g
.strings
["lucky_hack"] % {"base": base
.name
}
364 g
.map_screen
.show_message(text
)
366 # CPU complete dialogs.
368 for base
, new_cpus
in cpus_constructed
.iteritems():
369 if new_cpus
== len(base
.cpus
):
370 finished_cpus
= new_cpus
372 finished_cpus
= len([item
for item
in base
.cpus
373 if item
and item
.done
])
375 if finished_cpus
== len(base
.cpus
): # Finished all the CPUs.
376 text
= g
.strings
["item_construction_single"] % \
377 {"item": base
.cpus
[0].type.name
, "base": base
.name
}
378 g
.map_screen
.show_message(text
)
380 elif finished_cpus
== new_cpus
: # Finished the first batch of CPUs.
381 text
= g
.strings
["item_construction_batch"] % \
382 {"item": base
.cpus
[0].type.name
, "base": base
.name
}
383 g
.map_screen
.show_message(text
)
386 pass # No message unless we just finished the first or last CPU.
388 # Item complete dialogs.
389 for base
, item
in items_constructed
:
390 text
= g
.strings
["item_construction_single"] % \
391 {"item": item
.type.name
, "base": base
.name
}
392 g
.map_screen
.show_message(text
)
395 # If we just lost grace, show the warning.
396 if self
.had_grace
and not grace
:
397 self
.had_grace
= False
399 g
.map_screen
.show_message(g
.strings
["grace_warning"])
402 # Maintenance death, discovery, clear finished techs.
404 for base
in g
.all_bases():
407 # Maintenance deaths.
409 if self
.maintenance_cost
[cpu
] and base
.maintenance
[cpu
]:
410 self
.maintenance_cost
[cpu
] = \
411 max(0, self
.maintenance_cost
[cpu
]
412 - base
.maintenance
[cpu
])
413 #Chance of base destruction if cpu-unmaintained: 1.5%
414 if not dead
and g
.roll_chance(.015, secs_passed
):
415 dead_bases
.append( (base
, "maint") )
419 base_needs
= g
.current_share(base
.maintenance
[cash
],
420 time_of_day
, secs_passed
)
422 cash_maintenance
= max(0, cash_maintenance
- base_needs
)
423 #Chance of base destruction if cash-unmaintained: 1.5%
424 if not dead
and g
.roll_chance(.015, secs_passed
):
425 dead_bases
.append( (base
, "maint") )
429 if not (grace
or dead
or base
.has_grace()):
430 detect_chance
= base
.get_detect_chance()
432 print "Chance of discovery for base %s: %s" % \
433 (base
.name
, repr(detect_chance
))
435 for group
, chance
in detect_chance
.iteritems():
436 if g
.roll_chance(chance
/10000., secs_passed
):
437 dead_bases
.append( (base
, group
) )
441 # Clear finished techs
442 if base
.studying
in g
.techs
and g
.techs
[base
.studying
].done
:
445 self
.remove_bases(dead_bases
)
447 needed_cpu
= sum(self
.cpu_usage
.values())
448 if needed_cpu
> self
.available_cpus
[0]:
449 pct_left
= truediv(self
.available_cpus
[0], needed_cpu
)
450 for task
, cpu_assigned
in self
.cpu_usage
.iteritems():
451 self
.cpu_usage
[task
] = int(cpu_assigned
* pct_left
)
452 g
.map_screen
.needs_rebuild
= True
456 for event
in g
.events
:
457 if g
.roll_chance(g
.events
[event
].chance
/10000., time_sec
):
458 #Skip events already flagged as triggered.
459 if g
.events
[event
].triggered
== 1:
461 g
.events
[event
].trigger()
462 break # Don't trigger more than one at a time.
464 # And now process any complete days.
470 def recalc_cpu(self
):
471 from numpy
import array
472 self
.available_cpus
= array([0,0,0,0,0])
473 self
.sleeping_cpus
= 0
474 for base
in g
.all_bases():
476 if base
.power_state
in ["active", "overclocked", "suicide"]:
477 self
.available_cpus
[:base
.location
.safety
+1] += base
.cpu
478 elif base
.power_state
== "sleep":
479 self
.sleeping_cpus
+= base
.cpu
481 # Are we still in the grace period?
482 # The number of complete bases and complex_bases can be passed in, if we
484 def in_grace_period(self
, had_grace
= True, bases
= None,
485 complex_bases
= None):
486 # Did we already lose the grace period? We can't check self.had_grace
487 # directly, it may not exist yet.
492 if self
.raw_day
>= 23:
495 # Very Easy cops out here.
496 if self
.difficulty
< 3:
499 # Have we built metric ton of bases?
501 bases
= len([base
for base
in g
.all_bases() if base
.done
])
505 # That's enough for Easy
506 if self
.difficulty
< 5:
509 # Have we built a bunch of bases?
514 if self
.difficulty
== 5:
517 # Have we built any complicated bases?
518 # (currently Datacenter or above)
519 if complex_bases
== None:
520 complex_bases
= len([base
for base
in g
.all_bases()
522 and base
.is_complex()
524 if complex_bases
> 0:
527 # The sane people have left the building.
528 if self
.difficulty
<= 50:
531 # Hey, hey, what do you know? Impossible can get a useful number of
532 # bases before losing grace now. *tsk, tsk* We'll have to fix that.
538 #Run every day at midnight.
540 #interest and income.
541 self
.cash
+= (self
.interest_rate
* self
.cash
) / 10000
542 self
.cash
+= self
.income
545 for group
in self
.groups
.values():
548 def remove_bases(self
, dead_bases
):
550 # Reverse dead_bases to simplify deletion.
551 for base
, reason
in dead_bases
[::-1]:
552 base_name
= base
.name
554 if reason
== "maint":
555 dialog_string
= g
.strings
["discover_maint"] % \
558 elif reason
in self
.groups
:
559 discovery_locs
.append(base
.location
)
560 self
.groups
[reason
].discovered_a_base()
561 detect_phrase
= g
.strings
["discover_" + reason
]
563 dialog_string
= g
.strings
["discover"] % \
564 {"base": base_name
, "group": detect_phrase
}
566 print "Error: base destroyed for unknown reason: " + reason
567 dialog_string
= g
.strings
["discover"] % \
568 {"base": base_name
, "group": "???"}
572 g
.map_screen
.find_speed_button()
573 g
.map_screen
.needs_rebuild
= True
574 g
.map_screen
.show_message(dialog_string
, color
=gg
.colors
["red"])
576 # Now we update the internal information about what locations had
577 # the most recent discovery and the nextmost recent one. First,
578 # we filter out any locations of None, which shouldn't occur
579 # unless something bad's happening with base creation ...
580 discovery_locs
= [loc
for loc
in discovery_locs
if loc
]
583 # Now we handle the case where more than one discovery happened
584 # on a given tick. If that's the case, we need to arbitrarily
585 # pick two of them to be most recent and nextmost recent. So
586 # we shuffle the list and pick the first two for the dubious
588 if len(discovery_locs
) > 1:
589 random
.shuffle(discovery_locs
)
590 self
.last_discovery
= discovery_locs
[1]
591 self
.prev_discovery
= self
.last_discovery
592 self
.last_discovery
= discovery_locs
[0]
595 for group
in self
.groups
.values():
596 if group
.suspicion
> 10000:
597 # Someone discovered me.
600 # Check to see if the player has at least one CPU left. If not, they
601 # lose due to having no (complete) bases.
602 if self
.available_cpus
[0] + self
.sleeping_cpus
== 0:
603 # I have no usable bases left.
609 #returns the amount of cash available after taking into account all
610 #current projects in construction.
611 def future_cash(self
):
612 result_cash
= self
.cash
614 for base
in g
.all_bases():
615 result_cash
-= base
.cost_left
[0]
616 if g
.techs
.has_key(base
.studying
):
617 if not techs
.has_key(base
.studying
):
618 result_cash
-= g
.techs
[base
.studying
].cost_left
[0]
619 techs
[base
.studying
] = 1
620 if base
.cpus
and not base
.cpus
.done
:
621 result_cash
-= base
.cpus
.cost_left
[0]
622 for item
in base
.extra_items
:
623 if item
: result_cash
-= item
.cost_left
[0]