""" SCII - make ASCII RPG with multiple layered maps. Copyright (C) 2023 Mibi88 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/. """ # e-mail : mbcontact50@gmail.com #--- scii - make ASCII RPG with multiple layered maps --- # Map drawing modes STICKY = 0 CENTERED = 1 BLOCKS = 2 STICKYBLOCKS = 3 class Scii: def __init__(self, world, player, on_npc_collision, scii_keys = {"left": '4', "right": '6', "up": '8', "down": '2', "quit": 'q', "old_messages": '0'}, get_input_text = None, no_collision = " ^", collision_checker = None, message_history_max = 20, screen_width = 21, screen_height = 7, portrait_width = 7): self.world = world self.player = player self.map_num = world["map_num"] self.screen_width = screen_width self.screen_height = screen_height-1 self.screen_x_middle = self.screen_width//2 self.screen_y_middle = self.screen_height//2 self.screen_size = self.screen_width*self.screen_height self._vram = [' ' for i in range(self.screen_size)] self.on_char_handlers = {} self.scii_keys = scii_keys if get_input_text: self.get_input_text = get_input_text else: self.get_input_text = self._default_intext if collision_checker: self.collision_checker = collision_checker else: self.collision_checker = self._default_collision_check self.last_key = "" self.message_history_max = message_history_max self.old_messages = [] self.on_npc_collision = on_npc_collision self.no_collision = no_collision self.collision_npc = None self.portrait_width = portrait_width def _default_collision_check(self, world, player, x, y): collision = 0 for i in world["maps"][self.map_num]["layers"]: data = i["data"].split("\n") while("" in data): data.remove("") try: c = data[y][x] if c != i["transp_char"] and not (c in self.no_collision): return 1 for npc in world["npc"]: if npc["map"] == self.map_num and npc["x"] == x and npc["y"] == y and npc["collision_check"]: self.collision_npc = npc return 2 except: if x >= 0 and x < self.get_map_width(world["maps"][self.map_num]) and y >= 0 and y < self.get_map_height(world["maps"][self.map_num]): return 0 else: return 1 return 0 def _default_intext(self): return "> " def _input(self): try: i = input(self.get_input_text()) except: i = input(self.get_input_text(self)) if len(i) > 0: self.last_key = i[0] return self.last_key return self.last_key def _mainloop(self): key = "" while key != self.scii_keys["quit"]: self.draw_map(self.world["dmode"], self.player["isvisible"]) key = self._input() nx = self.player["x"] ny = self.player["y"] if key == self.scii_keys["left"]: nx = self.player["x"]-1 elif key == self.scii_keys["right"]: nx = self.player["x"]+1 elif key == self.scii_keys["up"]: ny = self.player["y"]-1 elif key == self.scii_keys["down"]: ny = self.player["y"]+1 collision = self.collision_checker(self.world, self.player, nx, ny) if self.player["collision_check"] and not collision: self.player["x"] = nx self.player["y"] = ny elif collision == 2 and self.collision_npc != None: self.on_npc_collision(self, self.collision_npc) for jump in self.world["maps"][self.map_num]["jumps"]: if jump["isactive"] and self.player["x"] == jump["x"] and self.player["y"] == jump["y"]: self.player["x"] = jump["to_x"] self.player["y"] = jump["to_y"] self.map_num = jump["to_map"] self.world["map_num"] = self.map_num if key == self.scii_keys["old_messages"]: self.show_old_messages() def mainloop(self): try: self._mainloop() except Exception as e: print("Ow there was an error in SCII : {}".format(e)) def _setc(self, x, y, c): if len(c) == 1 and x>=0 and x=0 and ymax_len: max_len = len(line) return max_len def get_map_height(self, map_data): max_len = 0 for i in map_data["layers"]: data = i["data"].split("\n") while("" in data): data.remove("") if len(data) > max_len: max_len = len(data) return max_len def draw_map(self, mode, show_player): map_data = self.world["maps"][self.map_num] osx = self.player["x"] osy = self.player["y"] sx = osx sy = osy if mode == STICKY: sx -= self.screen_x_middle sy -= self.screen_y_middle # Fix x if sx < 0: sx = 0 elif sx + self.screen_width > self.get_map_width(map_data): sx = self.get_map_width(map_data) - self.screen_width px = osx - sx # Fix y if sy < 0: sy = 0 elif sy + self.screen_height > self.get_map_height(map_data): sy = self.get_map_height(map_data) - self.screen_height py = osy - sy elif mode == CENTERED: px = self.screen_x_middle py = self.screen_y_middle sx -= self.screen_x_middle sy -= self.screen_y_middle elif mode == BLOCKS: sx = sx // self.screen_width * self.screen_width sy = sy // self.screen_height * self.screen_height px = osx-sx py = osy-sy elif mode == STICKYBLOCKS: sx = sx // self.screen_width * self.screen_width sy = sy // self.screen_height * self.screen_height if sx > self.get_map_width(map_data) - self.screen_width: sx = self.get_map_width(map_data) - self.screen_width if sy > self.get_map_height(map_data) - self.screen_height: sy = self.get_map_height(map_data) - self.screen_height px = osx-sx py = osy-sy self._cvram() layerc = 0 for i in map_data["layers"]: data = i["data"].split("\n") while("" in data): data.remove("") map_width = self.get_map_width(map_data) map_height = self.get_map_height(map_data) if "animations" in i: animations = 1 replace_animations = i["animations"]["replace_animations"] coords_animations = i["animations"]["coords_animations"] else: animations = 0 for y in range(self.screen_height): for x in range(self.screen_width): try: if sx+x >= 0 and sx+x < map_width and sy+y >= 0 and sy+y < map_height: c = data[sy+y][sx+x] if animations: if c in replace_animations: c = replace_animations[c]["frames"][replace_animations[c]["animation_frame"]] if "{}, {}".format(sx+x, sy+y) in coords_animations: c = coords_animations["{}, {}".format(sx+x, sy+y)]["frames"][coords_animations["{}, {}".format(sx+x, sy+y)]["animation_frame"]] if c != i["transp_char"]: self._setc(x, y, c) except: pass for npc in self.world["npc"]: if npc["layer"] == layerc and npc["map"] == self.map_num and npc["isvisible"]: self._setc(px + (npc["x"] - osx), py + (npc["y"] - osy), npc["char"]) if layerc == self.player["layer"] and show_player: self._setc(px, py, self.player["playerc"]) if animations: for key in i["animations"]["replace_animations"]: i["animations"]["replace_animations"][key]["animation_frame"] += 1 if i["animations"]["replace_animations"][key]["animation_frame"] >= len(i["animations"]["replace_animations"][key]["frames"]): i["animations"]["replace_animations"][key]["animation_frame"] = 0 for key in i["animations"]["coords_animations"]: i["animations"]["coords_animations"][key]["animation_frame"] += 1 if i["animations"]["coords_animations"][key]["animation_frame"] >= len(i["animations"]["coords_animations"][key]["frames"]): i["animations"]["coords_animations"][key]["animation_frame"] = 0 layerc += 1 self._dvram() def _show_text(self, text, portrait = None): if portrait: dwidth = self.screen_width - (2 + self.portrait_width) else: dwidth = self.screen_width - 2 self._cvram() dtext = "" sz = 0 y = 0 for i in text.split(" "): if len(i) > dwidth: count = sz for c in i: dtext += c count += 1 if count >= dwidth - 1: dtext += "-\n" count = 0 y += 1 dtext += ' ' sz = count + 1 elif sz + len(i) + 1 < dwidth: dtext += i + ' ' sz += len(i) + 1 y += i.count('\n') else: dtext += '\n' + i + ' ' sz = len(i) + 1 y += i.count('\n') + 1 x = 0 y = 0 msg = "" for i in dtext: if i == '\n': y += 1 x = 0 if y >= self.screen_height: if portrait: self._draw_portrait(portrait) self._setc(self.screen_width - 1, self.screen_y_middle, '>') self._dvram() intxt = input("Continue > ") if len(intxt) > 0: if intxt[0] == self.scii_keys["quit"]: return y = 0 x = 0 self._add_message(msg) msg = "" self._cvram() else: if portrait: self._setc(8 + x, y, i) else: self._setc(x, y, i) x += 1 msg += i if portrait: self._draw_portrait(portrait) self._dvram() if msg != "": self._add_message(msg) def show_text(self, text, portrait = None): self._show_text(text, portrait) input("End of the text > ") def _draw_portrait(self, portrait): for i in range(self.screen_height): self._setc(1, i, '|') self._setc(self.portrait_width, i, '|') for i in range(self.portrait_width): self._setc(1 + i, self.screen_height - 1, '-') self._setc(1 + i, 0, '-') self._setc(1, self.screen_height - 1, '+') self._setc(self.portrait_width, self.screen_height - 1, '+') self._setc(1, 0, '+') self._setc(self.portrait_width, 0, '+') portrait_data = portrait.split('\n') while("" in portrait_data): portrait_data.remove("") for y in range(1, self.screen_height - 1): for x in range(2, self.portrait_width): try: self._setc(x, y, portrait_data[y - 1][x - 2]) except: pass def _add_message(self, message): self.old_messages.append(message) while(len(self.old_messages) > self.message_history_max): del self.old_messages[0] def show_old_messages(self): if len(self.old_messages) < 1: self._cvram() x = 1 for i in "No messages.": self._setc(x, self.screen_y_middle, i) x += 1 self._dvram() input("Go back > ") c = len(self.old_messages) for i in [i for i in self.old_messages[::-1]]: self._show_text(i) c -= 1 if c < 1: input("Go back > ") else: intxt = input("Next message > ") if len(intxt) > 0: if intxt[0] == 'q': return def ask_choice(self, text, choices, portrait = None): choice = 0 while choice < 1 or choice > len(choices): newtext = text + "\n" c = 1 for i in choices: if c < len(choices): newtext += "{}:{}\n".format(c, i) else: newtext += "{}:{}".format(c, i) c += 1 self._show_text(newtext, portrait) try: choice = int(input("Choice > ")) except: choice = -1 self._add_message("You selected choice {}:{}".format(choice, choices[choice - 1])) return choice #---