playscii/game_room.py

224 lines
8.4 KiB
Python

from game_object import GameObject
class GameRoom:
"""
A collection of GameObjects within a GameWorld. Can be used to limit scope
of object updates, collisions, etc.
"""
camera_marker_name = ""
"If set, camera will move to marker with this name when room entered"
camera_follow_player = False
"If True, camera will follow player while in this room"
left_edge_warp_dest_name, right_edge_warp_dest_name = "", ""
"If set, warp to room OR marker with this name when edge crossed"
top_edge_warp_dest_name, bottom_edge_warp_dest_name = "", ""
warp_edge_bounds_obj_name = ""
'Object whose art\'s bounds should be used as our "edges" for above'
serialized = [
"name",
"camera_marker_name",
"left_edge_warp_dest_name",
"right_edge_warp_dest_name",
"top_edge_warp_dest_name",
"bottom_edge_warp_dest_name",
"warp_edge_bounds_obj_name",
"camera_follow_player",
]
"List of string names of members to serialize for this Room class."
log_changes = False
"Log changes to and from this room"
def __init__(self, world, name, room_data=None):
self.world = world
self.name = name
self.pre_first_update_run = False
# dict of objects by name:object
self.objects = {}
if not room_data:
return
# restore serialized properties
# TODO: this is copy-pasted from GameObject, find a way to unify
# TODO: GameWorld.set_data_for that takes instance, serialized list, data dict
for v in self.serialized:
if v not in room_data:
self.world.app.dev_log(
"Serialized property '{}' not found for room {}".format(
v, self.name
)
)
continue
if not hasattr(self, v):
setattr(self, v, None)
# match type of variable as declared, eg loc might be written as
# an int in the JSON so preserve its floatness
if getattr(self, v) is not None:
src_type = type(getattr(self, v))
setattr(self, v, src_type(room_data[v]))
else:
setattr(self, v, room_data[v])
# find objects by name and add them
for obj_name in room_data.get("objects", []):
self.add_object_by_name(obj_name)
def pre_first_update(self):
self.reset_edge_warps()
def reset_edge_warps(self):
self.edge_obj = self.world.objects.get(self.warp_edge_bounds_obj_name, None)
# no warping if we don't know our bounds
if not self.edge_obj:
return
edge_dest_name_suffix = "_name"
def set_edge_dest(dest_property):
# property name to destination name
dest_name = getattr(self, dest_property)
# get room OR object with name
dest_room = self.world.rooms.get(dest_name, None)
dest_obj = self.world.objects.get(dest_name, None)
# derive member name from serialized property name
member_name = dest_property.replace(edge_dest_name_suffix, "")
setattr(self, member_name, dest_room or dest_obj or None)
for pname in [
"left_edge_warp_dest_name",
"right_edge_warp_dest_name",
"top_edge_warp_dest_name",
"bottom_edge_warp_dest_name",
]:
set_edge_dest(pname)
def set_camera_marker_name(self, marker_name):
if marker_name not in self.world.objects:
self.world.app.log(
"Couldn't find camera marker with name {}".format(marker_name)
)
return
self.camera_marker_name = marker_name
if self is self.world.current_room:
self.use_camera_marker()
def use_camera_marker(self):
if self.camera_marker_name not in self.world.objects:
return
cam_mark = self.world.objects[self.camera_marker_name]
self.world.camera.set_loc_from_obj(cam_mark)
def entered(self, old_room):
"Run when the player enters this room."
if self.log_changes:
self.world.app.log('Room "{}" entered'.format(self.name))
# set camera if marker is set
if self.world.room_camera_changes_enabled:
self.use_camera_marker()
if self.camera_follow_player:
self.world.enable_player_camera_lock()
else:
self.world.disable_player_camera_lock()
# tell objects in this room player has entered so eg spawners can fire
for obj in self.objects.values():
obj.room_entered(self, old_room)
def exited(self, new_room):
"Run when the player exits this room."
if self.log_changes:
self.world.app.log('Room "{}" exited'.format(self.name))
# tell objects in this room player has exited
for obj in self.objects.values():
obj.room_exited(self, new_room)
def add_object_by_name(self, obj_name):
"Add object with given name to this room."
obj = self.world.objects.get(obj_name, None)
if not obj:
self.world.app.log("Couldn't find object named {}".format(obj_name))
return
self.add_object(obj)
def add_object(self, obj):
"Add object (by reference) to this room."
self.objects[obj.name] = obj
obj.rooms[self.name] = self
def remove_object_by_name(self, obj_name):
"Remove object with given name from this room."
obj = self.world.objects.get(obj_name, None)
if not obj:
self.world.app.log("Couldn't find object named {}".format(obj_name))
return
self.remove_object(obj)
def remove_object(self, obj):
"Remove object (by reference) from this room."
if obj.name in self.objects:
self.objects.pop(obj.name)
else:
self.world.app.log(
"GameRoom {} doesn't contain GameObject {}".format(self.name, obj.name)
)
if self.name in obj.rooms:
obj.rooms.pop(self.name)
else:
self.world.app.log(
"GameObject {} not found in GameRoom {}".format(obj.name, self.name)
)
def get_dict(self):
"Return a dict that GameWorld.save_to_file can dump to JSON"
object_names = list(self.objects.keys())
d = {"class_name": type(self).__name__, "objects": object_names}
# serialize whatever other vars are declared in self.serialized
for prop_name in self.serialized:
if hasattr(self, prop_name):
d[prop_name] = getattr(self, prop_name)
return d
def _check_edge_warp(self, game_object):
# bail if no bounds or edge warp destinations set
if not self.edge_obj:
return
if (
not self.left_edge_warp_dest
and not self.right_edge_warp_dest
and not self.top_edge_warp_dest
and not self.bottom_edge_warp_dest
):
return
if game_object.warped_recently():
return
px, py = game_object.x, game_object.y
if self.edge_obj.is_point_inside(px, py):
return
left, top, right, bottom = self.edge_obj.get_edges()
# which edge are we beyond?
warp_dest = None
if top > py > bottom and px < left:
warp_dest = self.left_edge_warp_dest
elif top > py > bottom and px > right:
warp_dest = self.right_edge_warp_dest
elif left < px < right and py > top:
warp_dest = self.top_edge_warp_dest
elif left < px < right and py < bottom:
warp_dest = self.bottom_edge_warp_dest
if not warp_dest:
return
if issubclass(type(warp_dest), GameRoom):
self.world.change_room(warp_dest.name)
elif issubclass(type(warp_dest), GameObject):
# TODO: change room or not? use_marker_room flag a la WarpTrigger?
game_object.set_loc(warp_dest.x, warp_dest.y)
game_object.last_warp_update = self.world.updates
def update(self):
if self is self.world.current_room:
self._check_edge_warp(self.world.player)
def destroy(self):
if self.name in self.world.rooms:
self.world.rooms.pop(self.name)
# remove references to us in each of our objects
for obj in self.objects.values():
obj.rooms.pop(self.name)
self.objects = {}