home · contact · privacy
Server: Make map generator selectable.
[plomrogue] / server / world.py
1 # This file is part of PlomRogue. PlomRogue is licensed under the GPL version 3
2 # or any later version. For details on its copyright, license, and warranties,
3 # see the file NOTICE in the root directory of the PlomRogue source package.
4
5
6 from server.config.world_data import world_db
7 from server.io import log
8 from server.utils import rand, libpr, c_pointer_to_bytearray
9 from server.utils import id_setter
10
11
12 def thingproliferation(t, prol_map):
13     """To chance of 1/TT_PROLIFERATE, create t offspring in open neighbor cell.
14
15     Naturally only works with TT_PROLIFERATE > 0. The neighbor cell must be be
16     marked "." in prol_map. If there are several map cell candidates, one is
17     selected randomly.
18     """
19     from server.config.world_data import directions_db, symbols_passable
20     from server.utils import mv_yx_in_dir_legal
21     prolscore = world_db["ThingTypes"][t["T_TYPE"]]["TT_PROLIFERATE"]
22     if prolscore and (1 == prolscore or 1 == (rand.next() % prolscore)):
23         candidates = []
24         for dir in [directions_db[key] for key in sorted(directions_db.keys())]:
25             mv_result = mv_yx_in_dir_legal(dir, t["T_POSY"], t["T_POSX"])
26             c = prol_map[mv_result[1] + world_db["MAP_LENGTH"] + mv_result[2]]
27             if mv_result[0] and str(c) in symbols_passable:
28                 candidates.append((mv_result[1], mv_result[2]))
29         if len(candidates):
30             i = rand.next() % len(candidates)
31             id = id_setter(-1, "Things")
32             newT = new_Thing(t["T_TYPE"], (candidates[i][0], candidates[i][1]))
33             world_db["Things"][id] = newT
34
35
36 def update_map_memory(t, age_map=True):
37     """Update t's T_MEMMAP with what's in its FOV now,age its T_MEMMEPTHMAP."""
38
39     def age_some_memdepthmap_on_nonfov_cells():
40         # OUTSOURCED FOR PERFORMANCE REASONS TO libplomrogue.so:
41         # ord_v = ord("v")
42         # ord_0 = ord("0")
43         # ord_9 = ord("9")
44         # for pos in [pos for pos in range(world_db["MAP_LENGTH"] ** 2)
45         #             if not ord_v == t["fovmap"][pos]
46         #             if ord_0 <= t["T_MEMDEPTHMAP"][pos]
47         #             if ord_9 > t["T_MEMDEPTHMAP"][pos]
48         #             if not rand.next() % (2 **
49         #                                   (t["T_MEMDEPTHMAP"][pos] - 48))]:
50         #     t["T_MEMDEPTHMAP"][pos] += 1
51         memdepthmap = c_pointer_to_bytearray(t["T_MEMDEPTHMAP"])
52         fovmap = c_pointer_to_bytearray(t["fovmap"])
53         libpr.age_some_memdepthmap_on_nonfov_cells(memdepthmap, fovmap)
54
55     if not t["T_MEMMAP"]:
56         t["T_MEMMAP"] = bytearray(b' ' * (world_db["MAP_LENGTH"] ** 2))
57     if not t["T_MEMDEPTHMAP"]:
58         t["T_MEMDEPTHMAP"] = bytearray(b' ' * (world_db["MAP_LENGTH"] ** 2))
59     ord_v = ord("v")
60     ord_0 = ord("0")
61     for pos in [pos for pos in range(world_db["MAP_LENGTH"] ** 2)
62                 if ord_v == t["fovmap"][pos]]:
63         t["T_MEMDEPTHMAP"][pos] = ord_0
64         t["T_MEMMAP"][pos] = world_db["MAP"][pos]
65     if age_map:
66         age_some_memdepthmap_on_nonfov_cells()
67     t["T_MEMTHING"] = [mt for mt in t["T_MEMTHING"]
68                        if ord_v != t["fovmap"][(mt[1] * world_db["MAP_LENGTH"])
69                                                + mt[2]]]
70     for id in [id for id in world_db["Things"]
71                if not world_db["Things"][id]["carried"]]:
72         type = world_db["Things"][id]["T_TYPE"]
73         if not world_db["ThingTypes"][type]["TT_LIFEPOINTS"]:
74             y = world_db["Things"][id]["T_POSY"]
75             x = world_db["Things"][id]["T_POSX"]
76             if ord_v == t["fovmap"][(y * world_db["MAP_LENGTH"]) + x]:
77                 t["T_MEMTHING"].append((type, y, x))
78
79
80 def build_fov_map(t):
81     """Build Thing's FOV map."""
82     t["fovmap"] = bytearray(b'v' * (world_db["MAP_LENGTH"] ** 2))
83     fovmap = c_pointer_to_bytearray(t["fovmap"])
84     map = c_pointer_to_bytearray(world_db["MAP"])
85     if libpr.build_fov_map(t["T_POSY"], t["T_POSX"], fovmap, map):
86         raise RuntimeError("Malloc error in build_fov_Map().")
87
88
89 def new_Thing(_type, pos=(0, 0)):
90     """Return Thing of type T_TYPE, with fovmap if alive and world active."""
91     from server.config.world_data import thing_defaults
92     thing = {}
93     for key in thing_defaults:
94         thing[key] = thing_defaults[key]
95         if type(thing[key]) == list:
96             thing[key] = thing[key][:]
97     thing["T_LIFEPOINTS"] = world_db["ThingTypes"][_type]["TT_LIFEPOINTS"]
98     thing["T_TYPE"] = _type
99     thing["T_POSY"] = pos[0]
100     thing["T_POSX"] = pos[1]
101     if world_db["WORLD_ACTIVE"] and thing["T_LIFEPOINTS"]:
102         build_fov_map(thing)
103     return thing
104
105
106 def decrement_lifepoints(t):
107     """Decrement t's lifepoints by 1, and if to zero, corpse it.
108
109     If t is the player avatar, only blank its fovmap, so that the client may
110     still display memory data. On non-player things, erase fovmap and memory.
111     Dying actors drop all their things.
112     """
113     t["T_LIFEPOINTS"] -= 1
114     if 0 == t["T_LIFEPOINTS"]:
115         for id in t["T_CARRIES"]:
116             t["T_CARRIES"].remove(id)
117             world_db["Things"][id]["T_POSY"] = t["T_POSY"]
118             world_db["Things"][id]["T_POSX"] = t["T_POSX"]
119             world_db["Things"][id]["carried"] = False
120         t["T_TYPE"] = world_db["ThingTypes"][t["T_TYPE"]]["TT_CORPSE_ID"]
121         if world_db["Things"][0] == t:
122             t["fovmap"] = bytearray(b' ' * (world_db["MAP_LENGTH"] ** 2))
123             log("You die.")
124             log("See README on how to start over.")
125         else:
126             t["fovmap"] = False
127             t["T_MEMMAP"] = False
128             t["T_MEMDEPTHMAP"] = False
129             t["T_MEMTHING"] = []
130
131
132 def try_healing(t):
133     """If t's HP < max, increment them if well-nourished, maybe waiting."""
134     if t["T_LIFEPOINTS"] < \
135        world_db["ThingTypes"][t["T_TYPE"]]["TT_LIFEPOINTS"]:
136         wait_id = [id for id in world_db["ThingActions"]
137                       if world_db["ThingActions"][id]["TA_NAME"] == "wait"][0]
138         wait_divider = 8 if t["T_COMMAND"] == wait_id else 1
139         testval = int(abs(t["T_SATIATION"]) / wait_divider)
140         if (testval <= 1 or 1 == (rand.next() % testval)):
141             t["T_LIFEPOINTS"] += 1
142             if t == world_db["Things"][0]:
143                 log("You HEAL.")
144
145
146 def hunger_per_turn(type_id):
147     """The amount of satiation score lost per turn for things of given type."""
148     import math
149     return int(math.sqrt(world_db["ThingTypes"][type_id]["TT_LIFEPOINTS"]))
150
151
152 def hunger(t):
153     """Decrement t's satiation,dependent on it trigger lifepoint dec chance."""
154     if t["T_SATIATION"] > -32768:
155         t["T_SATIATION"] -= hunger_per_turn(t["T_TYPE"])
156     if 0 != t["T_SATIATION"] and 0 == int(rand.next() / abs(t["T_SATIATION"])):
157         if t == world_db["Things"][0]:
158             if t["T_SATIATION"] < 0:
159                 log("You SUFFER from hunger.")
160             else:
161                 log("You SUFFER from over-eating.")
162         decrement_lifepoints(t)
163
164
165 def set_world_inactive():
166     """Set world_db["WORLD_ACTIVE"] to 0 and remove worldstate file."""
167     from server.io import safely_remove_worldstate_file
168     safely_remove_worldstate_file()
169     world_db["WORLD_ACTIVE"] = 0
170
171
172 def make_world(seed):
173     """(Re-)build game world, i.e. map, things, to a new turn 1 from seed.
174
175     Seed rand with seed. Do more only with a "wait" ThingAction and
176     world["PLAYER_TYPE"] matching ThingType of TT_START_NUMBER > 0. Then,
177     world_db["Things"] emptied, call make_map() and set
178     world_db["WORLD_ACTIVE"], world_db["TURN"] to 1. Build new Things
179     according to ThingTypes' TT_START_NUMBERS, with Thing of ID 0 to ThingType
180     of ID = world["PLAYER_TYPE"]. Place Things randomly, and actors not on each
181     other. Init player's memory map. Write "NEW_WORLD" line to out file.
182     """
183     from server.config.world_data import symbols_passable
184     from server.config.misc import make_map_func
185
186     def free_pos():
187         i = 0
188         while 1:
189             err = "Space to put thing on too hard to find. Map too small?"
190             while 1:
191                 y = rand.next() % world_db["MAP_LENGTH"]
192                 x = rand.next() % world_db["MAP_LENGTH"]
193                 if chr(world_db["MAP"][y * world_db["MAP_LENGTH"] + x]) in \
194                     symbols_passable:
195                     break
196                 i += 1
197                 if i == 65535:
198                     raise SystemExit(err)
199             # Replica of C code, wrongly ignores animatedness of new Thing.
200             pos_clear = (0 == len([id for id in world_db["Things"]
201                                    if world_db["Things"][id]["T_LIFEPOINTS"]
202                                    if world_db["Things"][id]["T_POSY"] == y
203                                    if world_db["Things"][id]["T_POSX"] == x]))
204             if pos_clear:
205                 break
206         return (y, x)
207
208     rand.seed = seed 
209     if world_db["MAP_LENGTH"] < 1:
210         print("Ignoring: No map length >= 1 defined.")
211         return
212     libpr.set_maplength(world_db["MAP_LENGTH"])
213     player_will_be_generated = False
214     playertype = world_db["PLAYER_TYPE"]
215     for ThingType in world_db["ThingTypes"]:
216         if playertype == ThingType:
217             if 0 < world_db["ThingTypes"][ThingType]["TT_START_NUMBER"]:
218                 player_will_be_generated = True
219             break
220     if not player_will_be_generated:
221         print("Ignoring: No player type with start number >0 defined.")
222         return
223     wait_action = False
224     for ThingAction in world_db["ThingActions"]:
225         if "wait" == world_db["ThingActions"][ThingAction]["TA_NAME"]:
226             wait_action = True
227     if not wait_action:
228         print("Ignoring beyond SEED_MAP: " +
229               "No thing action with name 'wait' defined.")
230         return
231     world_db["Things"] = {}
232     make_map_func()
233     world_db["WORLD_ACTIVE"] = 1
234     world_db["TURN"] = 1
235     for i in range(world_db["ThingTypes"][playertype]["TT_START_NUMBER"]):
236         id = id_setter(-1, "Things")
237         world_db["Things"][id] = new_Thing(playertype, free_pos())
238     if not world_db["Things"][0]["fovmap"]:
239         empty_fovmap = bytearray(b" " * world_db["MAP_LENGTH"] ** 2)
240         world_db["Things"][0]["fovmap"] = empty_fovmap
241     update_map_memory(world_db["Things"][0])
242     for type in world_db["ThingTypes"]:
243         for i in range(world_db["ThingTypes"][type]["TT_START_NUMBER"]):
244             if type != playertype:
245                 id = id_setter(-1, "Things")
246                 world_db["Things"][id] = new_Thing(type, free_pos())
247     from server.config.io import io_db
248     from server.io import strong_write
249     strong_write(io_db["file_out"], "NEW_WORLD\n")
250
251
252 def turn_over():
253     """Run game world and its inhabitants until new player input expected."""
254     from server.config.actions import action_db, ai_func
255     id = 0
256     whilebreaker = False
257     while world_db["Things"][0]["T_LIFEPOINTS"]:
258         proliferable_map = world_db["MAP"][:]
259         for id in [id for id in world_db["Things"]
260                    if not world_db["Things"][id]["carried"]]:
261             y = world_db["Things"][id]["T_POSY"]
262             x = world_db["Things"][id]["T_POSX"]
263             proliferable_map[y * world_db["MAP_LENGTH"] + x] = ord('X')
264         for id in [id for id in world_db["Things"]]:  # Only what's from start!
265             if not id in world_db["Things"] or \
266                world_db["Things"][id]["carried"]:   # May have been consumed or
267                 continue                            # picked up during turn …
268             Thing = world_db["Things"][id]
269             if Thing["T_LIFEPOINTS"]:
270                 if not Thing["T_COMMAND"]:
271                     update_map_memory(Thing)
272                     if 0 == id:
273                         whilebreaker = True
274                         break
275                     ai_func(Thing)
276                 try_healing(Thing)
277                 hunger(Thing)
278                 if Thing["T_LIFEPOINTS"]:
279                     Thing["T_PROGRESS"] += 1
280                     taid = [a for a in world_db["ThingActions"]
281                               if a == Thing["T_COMMAND"]][0]
282                     ThingAction = world_db["ThingActions"][taid]
283                     if Thing["T_PROGRESS"] == ThingAction["TA_EFFORT"]:
284                         action = action_db["actor_" + ThingAction["TA_NAME"]]
285                         action(Thing)
286                         Thing["T_COMMAND"] = 0
287                         Thing["T_PROGRESS"] = 0
288             thingproliferation(Thing, proliferable_map)
289         if whilebreaker:
290             break
291         world_db["TURN"] += 1