libSCII/LIBSCII.py

321 lines
12 KiB
Python

"""
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<self.screen_width and y>=0 and y<self.screen_height:
self._vram[y*self.screen_width+x] = c
def _dvram(self):
for y in range(self.screen_height):
for x in range(self.screen_width):
print(self._vram[y*self.screen_width+x], end="")
print()
def _cvram(self):
self._vram = [' ' for i in range(self.screen_size)]
def get_map_width(self, map_data):
max_len = 0
for i in map_data["layers"]:
for line in i["data"].split('\n'):
if len(line)>max_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
#---