7DRL 2025 progress
This commit is contained in:
parent
e928dda4b3
commit
6be474da08
Binary file not shown.
Before Width: | Height: | Size: 662 KiB After Width: | Height: | Size: 674 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -28,7 +28,6 @@ sf::Sprite PyTexture::sprite(int index, sf::Vector2f pos, sf::Vector2f s)
|
|||
|
||||
PyObject* PyTexture::pyObject()
|
||||
{
|
||||
std::cout << "Find type" << std::endl;
|
||||
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture");
|
||||
PyObject* obj = PyTexture::pynew(type, Py_None, Py_None);
|
||||
|
||||
|
|
|
@ -249,7 +249,7 @@ int UICaption::init(PyUICaptionObject* self, PyObject* args, PyObject* kwds)
|
|||
self->data->text.setPosition(pos_result->data);
|
||||
// check types for font, fill_color, outline_color
|
||||
|
||||
std::cout << PyUnicode_AsUTF8(PyObject_Repr(font)) << std::endl;
|
||||
//std::cout << PyUnicode_AsUTF8(PyObject_Repr(font)) << std::endl;
|
||||
if (font != NULL && !PyObject_IsInstance(font, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Font")/*(PyObject*)&PyFontType)*/)){
|
||||
PyErr_SetString(PyExc_TypeError, "font must be a mcrfpy.Font instance");
|
||||
return -1;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import mcrfpy
|
||||
import random
|
||||
#t = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16)
|
||||
t = mcrfpy.Texture("assets/kenney_TD_MR_IP.png", 16, 16)
|
||||
#def iterable_entities(grid):
|
||||
|
@ -37,7 +38,7 @@ class COSEntity(): #mcrfpy.Entity): # Fake mcrfpy.Entity integration; engine bu
|
|||
self._entity.sprite_number = value
|
||||
|
||||
def __repr__(self):
|
||||
return f"<COSEntity ({self.draw_pos}) on {self.grid}>"
|
||||
return f"<{self.__class__.__name__} ({self.draw_pos})>"
|
||||
|
||||
def die(self):
|
||||
# ugly workaround! grid.entities isn't really iterable (segfaults)
|
||||
|
@ -69,7 +70,9 @@ class COSEntity(): #mcrfpy.Entity): # Fake mcrfpy.Entity integration; engine bu
|
|||
for e in self.game.entities:
|
||||
if e is self: continue
|
||||
if e.draw_pos == (tx, ty): e.ev_enter(self)
|
||||
|
||||
|
||||
def act(self):
|
||||
pass
|
||||
|
||||
def ev_enter(self, other):
|
||||
pass
|
||||
|
@ -106,11 +109,92 @@ class COSEntity(): #mcrfpy.Entity): # Fake mcrfpy.Entity integration; engine bu
|
|||
#self.draw_pos = (tx, ty)
|
||||
self.do_move(tx, ty)
|
||||
|
||||
class Equippable:
|
||||
def __init__(self, hands = 0, hp_healing = 0, damage = 0, defense = 0, zap_damage = 1, zap_cooldown = 10, sprite = 129):
|
||||
self.hands = hands
|
||||
self.hp_healing = hp_healing
|
||||
self.damage = damage
|
||||
self.defense = defense
|
||||
self.zap_damage = zap_damage
|
||||
self.zap_cooldown = zap_cooldown
|
||||
self.zap_cooldown_remaining = 0
|
||||
self.sprite = self.sprite
|
||||
self.quality = 0
|
||||
|
||||
def tick(self):
|
||||
if self.zap_cooldown_remaining:
|
||||
self.zap_cooldown_remaining -= 1
|
||||
if self.zap_cooldown_remaining < 0: self.zap_cooldown_remaining = 0
|
||||
|
||||
def __repr__(self):
|
||||
cooldown_str = f'({self.zap_cooldown_remaining} rounds until ready)'
|
||||
return f"<Equippable hands={self.hands}, hp_healing={self.hp_healing}, damage={self.damage}, defense={self.defense}, zap_damage={self.zap_damage}, zap_cooldown={self.zap_cooldown}{cooldown_str if self.zap_cooldown_remaining else ''}, sprite={self.sprite}>"
|
||||
|
||||
def classify(self):
|
||||
categories = []
|
||||
if self.hands==0:
|
||||
categories.append("consumable")
|
||||
elif self.damage > 0:
|
||||
categories.append(f"{self.hands}-handed weapon")
|
||||
elif self.defense > 0:
|
||||
categories.append(f"defense")
|
||||
elif self.zap_damage > 0:
|
||||
categories.append("{self.hands}-handed magic weapon")
|
||||
if len(categories) == 0:
|
||||
return "unclassifiable"
|
||||
elif len(categories) == 1:
|
||||
return categories[0]
|
||||
else:
|
||||
return "Erratic: " + ', '.join(categories)
|
||||
|
||||
#def compare(self, other):
|
||||
# my_class = self.classify()
|
||||
# o_class = other.classify()
|
||||
# if my_class == "unclassifiable" or o_class == "unclassifiable":
|
||||
# return None
|
||||
# if my_class == "consumable":
|
||||
# return other.hp_healing - self.hp_healing
|
||||
|
||||
|
||||
class PlayerEntity(COSEntity):
|
||||
def __init__(self, *, game):
|
||||
#print(f"spawn at origin")
|
||||
self.draw_order = 10
|
||||
super().__init__(game.grid, 0, 0, sprite_num=84, game=game)
|
||||
self.hp = 10
|
||||
self.max_hp = 10
|
||||
self.base_damage = 1
|
||||
self.base_defense = 0
|
||||
self.luck = 0
|
||||
self.archetype = None
|
||||
self.equipped = []
|
||||
self.inventory = []
|
||||
|
||||
def tick(self):
|
||||
for i in self.equipped:
|
||||
i.tick()
|
||||
|
||||
def calc_damage(self):
|
||||
dmg = self.base_damage
|
||||
for i in self.equipped:
|
||||
dmg += i.damage
|
||||
return dmg
|
||||
|
||||
def calc_defense(self):
|
||||
defense = self.base_defense
|
||||
for i in self.equipped:
|
||||
defense += i.damage
|
||||
return defense
|
||||
|
||||
def do_zap(self):
|
||||
pass
|
||||
|
||||
def bump(self, other, dx, dy, test=False):
|
||||
if type(other) == BoulderEntity:
|
||||
print("Boulder hit w/ knockback!")
|
||||
return self.game.pull_boulder_move((-dx, -dy), other)
|
||||
print(f"oof, ouch, {other} bumped the player - {other.base_damage} damage from {other}")
|
||||
self.hp = max(self.hp - max(other.base_damage - self.calc_defense(), 0), 0)
|
||||
|
||||
def respawn(self, avoid=None):
|
||||
# find spawn point
|
||||
|
@ -142,6 +226,8 @@ class BoulderEntity(COSEntity):
|
|||
if type(other) == BoulderEntity:
|
||||
#print("Boulders can't push boulders")
|
||||
return False
|
||||
elif type(other) == EnemyEntity:
|
||||
if not other.can_push: return False
|
||||
#tx, ty = int(self.e.position[0] + dx), int(self.e.position[1] + dy)
|
||||
tx, ty = int(self.draw_pos[0] + dx), int(self.draw_pos[1] + dy)
|
||||
# Is the boulder blocked the same direction as the bumper? If not, let's both move
|
||||
|
@ -155,7 +241,7 @@ class BoulderEntity(COSEntity):
|
|||
class ButtonEntity(COSEntity):
|
||||
def __init__(self, x, y, exit_entity, *, game):
|
||||
self.draw_order = 1
|
||||
super().__init__(game.grid, x, y, 42, game=game)
|
||||
super().__init__(game.grid, x, y, 250, game=game)
|
||||
self.exit = exit_entity
|
||||
|
||||
def ev_enter(self, other):
|
||||
|
@ -171,7 +257,8 @@ class ButtonEntity(COSEntity):
|
|||
# self.exit.unlock()
|
||||
# TODO: unlock, and then lock again, when player steps on/off
|
||||
if not test:
|
||||
other._relative_move(dx, dy)
|
||||
pos = int(self.draw_pos[0]), int(self.draw_pos[1])
|
||||
other.do_move(*pos)
|
||||
return True
|
||||
|
||||
class ExitEntity(COSEntity):
|
||||
|
@ -199,5 +286,108 @@ class ExitEntity(COSEntity):
|
|||
other._relative_move(dx, dy)
|
||||
#TODO - player go down a level logic
|
||||
if type(other) == PlayerEntity:
|
||||
self.game.create_level(self.game.depth + 1)
|
||||
self.game.depth += 1
|
||||
print(f"welcome to level {self.game.depth}")
|
||||
self.game.create_level(self.game.depth)
|
||||
self.game.swap_level(self.game.level, self.game.spawn_point)
|
||||
|
||||
class EnemyEntity(COSEntity):
|
||||
def __init__(self, x, y, hp=2, base_damage=1, base_defense=0, sprite=123, can_push=False, crushable=True, sight=8, move_cooldown=1, *, game):
|
||||
self.draw_order = 7
|
||||
super().__init__(game.grid, x, y, sprite, game=game)
|
||||
self.hp = hp
|
||||
self.base_damage = base_damage
|
||||
self.base_defense = base_defense
|
||||
self.base_sprite = sprite
|
||||
self.can_push = can_push
|
||||
self.crushable = crushable
|
||||
self.sight = sight
|
||||
self.move_cooldown = move_cooldown
|
||||
self.moved_last = 0
|
||||
|
||||
def bump(self, other, dx, dy, test=False):
|
||||
if self.hp == 0:
|
||||
if not test:
|
||||
old_pos = int(self.draw_pos[0]), int(self.draw_pos[1])
|
||||
other.do_move(*old_pos)
|
||||
return True
|
||||
if type(other) == PlayerEntity:
|
||||
# TODO - get damage from player, take damage, decide to die or not
|
||||
d = other.calc_damage()
|
||||
self.hp -= d
|
||||
self.hp = max(self.hp, 0)
|
||||
if self.hp == 0:
|
||||
self._entity.sprite_number = self.base_sprite + 246
|
||||
self.draw_order = 1
|
||||
print(f"Player hit for {d}. HP = {self.hp}")
|
||||
#self.hp = 0
|
||||
return False
|
||||
elif type(other) == BoulderEntity:
|
||||
if not self.crushable and self.hp > 0:
|
||||
print("Uncrushable!")
|
||||
return False
|
||||
if self.hp > 0:
|
||||
print("Ouch, my entire body!!")
|
||||
self._entity.sprite_number = self.base_sprite + 246
|
||||
self.hp = 0
|
||||
old_pos = int(self.draw_pos[0]), int(self.draw_pos[1])
|
||||
if not test:
|
||||
other.do_move(*old_pos)
|
||||
return True
|
||||
|
||||
def act(self):
|
||||
if self.hp > 0:
|
||||
# if player nearby: attack
|
||||
x, y = self.draw_pos
|
||||
px, py = self.game.player.draw_pos
|
||||
for d in ((1, 0), (0, 1), (-1, 0), (1, 0)):
|
||||
if int(x + d[0]) == int(px) and int(y + d[1]) == int(py):
|
||||
self.try_move(*d)
|
||||
return
|
||||
|
||||
# slow movement (doesn't affect ability to attack)
|
||||
if self.moved_last < 0:
|
||||
self.moved_last -= 1
|
||||
return
|
||||
else:
|
||||
self.moved_last = self.move_cooldown
|
||||
|
||||
# if player is not nearby, wander
|
||||
if abs(x - px) + abs(y - py) > self.sight:
|
||||
d = random.choice(((1, 0), (0, 1), (-1, 0), (1, 0)))
|
||||
self.try_move(*d)
|
||||
|
||||
# if can_push and player in a line: KICK
|
||||
if self.can_push:
|
||||
if int(x) == int(px):
|
||||
pass # vertical kick
|
||||
elif int(y) == int(py):
|
||||
pass # horizontal kick
|
||||
|
||||
# else, nearby pursue
|
||||
towards = []
|
||||
dist = lambda dx, dy: abs(px - (x + dx)) + abs(py - (y + dy))
|
||||
current_dist = dist(0, 0)
|
||||
for d in ((1, 0), (0, 1), (-1, 0), (1, 0)):
|
||||
if dist(*d) <= current_dist + 0.75: towards.append(d)
|
||||
print(current_dist, towards)
|
||||
target_dir = random.choice(towards)
|
||||
self.try_move(*target_dir)
|
||||
|
||||
|
||||
class TreasureEntity(COSEntity):
|
||||
def __init__(self, x, y, treasure_table=None, *, game):
|
||||
self.draw_order = 6
|
||||
super().__init__(game.grid, x, y, 89, game=game)
|
||||
self.popped = False
|
||||
|
||||
def bump(self, other, dx, dy, test=False):
|
||||
if type(other) != PlayerEntity:
|
||||
return False
|
||||
if self.popped:
|
||||
print("It's already open.")
|
||||
return
|
||||
print("Take me, I'm yours!")
|
||||
self._entity.sprite_number = 91
|
||||
self.popped = True
|
||||
|
||||
|
|
|
@ -25,6 +25,10 @@ class BinaryRoomNode:
|
|||
def __repr__(self):
|
||||
return f"<RoomNode {self.data}>"
|
||||
|
||||
def center(self):
|
||||
x, y, w, h = self.data
|
||||
return (x + w // 2, y + h // 2)
|
||||
|
||||
def split(self):
|
||||
new_data = binary_space_partition(*self.data)
|
||||
self.left = BinaryRoomNode(new_data[0])
|
||||
|
@ -35,6 +39,11 @@ class BinaryRoomNode:
|
|||
return self.left.walk() + self.right.walk()
|
||||
return [self]
|
||||
|
||||
def contains(self, pt):
|
||||
x, y, w, h = self.data
|
||||
tx, ty = pt
|
||||
return x <= tx <= x + w and y <= ty <= y + h
|
||||
|
||||
class RoomGraph:
|
||||
def __init__(self, xywh):
|
||||
self.root = BinaryRoomNode(xywh)
|
||||
|
@ -49,6 +58,7 @@ class RoomGraph:
|
|||
|
||||
def room_coord(room, margin=0):
|
||||
x, y, w, h = room.data
|
||||
#print(x,y,w,h, f'{margin=}', end=';')
|
||||
w -= 1
|
||||
h -= 1
|
||||
margin += 1
|
||||
|
@ -58,10 +68,37 @@ def room_coord(room, margin=0):
|
|||
h -= margin
|
||||
if w < 0: w = 0
|
||||
if h < 0: h = 0
|
||||
#print(x,y,w,h, end=' -> ')
|
||||
tx = x if w==0 else random.randint(x, x+w)
|
||||
ty = y if h==0 else random.randint(y, y+h)
|
||||
#print((tx, ty))
|
||||
return (tx, ty)
|
||||
|
||||
def adjacent_rooms(r, rooms):
|
||||
x, y, w, h = r.data
|
||||
adjacents = {}
|
||||
|
||||
for i, other_r in enumerate(rooms):
|
||||
rx, ry, rw, rh = other_r.data
|
||||
if (rx, ry, rw, rh) == r:
|
||||
continue # Skip self
|
||||
|
||||
# Check vertical adjacency (above or below)
|
||||
if rx < x + w and x < rx + rw: # Overlapping width
|
||||
if ry + rh == y: # Above
|
||||
adjacents[i] = (x + w // 2, y - 1)
|
||||
elif y + h == ry: # Below
|
||||
adjacents[i] = (x + w // 2, y + h + 1)
|
||||
|
||||
# Check horizontal adjacency (left or right)
|
||||
if ry < y + h and y < ry + rh: # Overlapping height
|
||||
if rx + rw == x: # Left
|
||||
adjacents[i] = (x - 1, y + h // 2)
|
||||
elif x + w == rx: # Right
|
||||
adjacents[i] = (x + w + 1, y + h // 2)
|
||||
|
||||
return adjacents
|
||||
|
||||
class Level:
|
||||
def __init__(self, width, height):
|
||||
self.width = width
|
||||
|
@ -70,6 +107,7 @@ class Level:
|
|||
self.graph = RoomGraph( (0, 0, width, height) )
|
||||
self.grid = mcrfpy.Grid(width, height, t, (10, 10), (1014, 758))
|
||||
self.highlighted = -1 #debug view feature
|
||||
self.walled_rooms = [] # for tracking "hallway rooms" vs "walled rooms"
|
||||
|
||||
def fill(self, xywh, highlight = False):
|
||||
if highlight:
|
||||
|
@ -84,7 +122,7 @@ class Level:
|
|||
def highlight(self, delta):
|
||||
rooms = self.graph.walk()
|
||||
if self.highlighted < len(rooms):
|
||||
print(f"reset {self.highlighted}")
|
||||
#print(f"reset {self.highlighted}")
|
||||
self.fill(rooms[self.highlighted].data) # reset
|
||||
self.highlighted += delta
|
||||
print(f"highlight {self.highlighted}")
|
||||
|
@ -110,7 +148,7 @@ class Level:
|
|||
|
||||
def fill_rooms(self, features=None):
|
||||
rooms = self.graph.walk()
|
||||
print(f"rooms: {len(rooms)}")
|
||||
#print(f"rooms: {len(rooms)}")
|
||||
for i, g in enumerate(rooms):
|
||||
X, Y, W, H = g.data
|
||||
#c = [random.randint(0, 255) for _ in range(3)]
|
||||
|
@ -120,9 +158,14 @@ class Level:
|
|||
self.grid.at((x, y)).tilesprite = ts
|
||||
|
||||
def wall_rooms(self):
|
||||
self.walled_rooms = []
|
||||
rooms = self.graph.walk()
|
||||
for g in rooms:
|
||||
#if random.random() > 0.66: continue
|
||||
for i, g in enumerate(rooms):
|
||||
# unwalled / hallways: not selected for small dungeons, first, last, and 65% of all other rooms
|
||||
if len(rooms) > 3 and i > 1 and i < len(rooms) - 2 and random.random() < 0.35:
|
||||
self.walled_rooms.append(False)
|
||||
continue
|
||||
self.walled_rooms.append(True)
|
||||
X, Y, W, H = g.data
|
||||
for x in range(X, X+W):
|
||||
self.grid.at((x, Y)).walkable = False
|
||||
|
@ -138,13 +181,45 @@ class Level:
|
|||
# self.grid.at((0, y)).walkable = False
|
||||
self.grid.at((self.width-1, y)).walkable = False
|
||||
|
||||
def generate(self, target_rooms = 5, features=None):
|
||||
if features is None:
|
||||
shuffled = ["boulder", "button"]
|
||||
random.shuffle(shuffled)
|
||||
features = ["spawn"] + shuffled + ["exit", "treasure"]
|
||||
# Binary space partition to reach given # of rooms
|
||||
def dig_path(self, start:"Tuple[int, int]", end:"Tuple[int, int]", walkable=True, color=None, sprite=None):
|
||||
print(f"Digging: {start} -> {end}")
|
||||
# get x1,y1 and x2,y2 coordinates: top left and bottom right points on the rect formed by two random points, one from each of the 2 rooms
|
||||
x1 = min([start[0], end[0]])
|
||||
x2 = max([start[0], end[0]])
|
||||
dw = x2 - x1
|
||||
y1 = min([start[1], end[1]])
|
||||
y2 = max([start[1], end[1]])
|
||||
dh = y2 - y1
|
||||
|
||||
# random: top left or bottom right as the corner between the paths
|
||||
tx, ty = (x1, y1) if random.random() >= 0.5 else (x2, y2)
|
||||
|
||||
for x in range(x1, x1+dw):
|
||||
try:
|
||||
if walkable:
|
||||
self.grid.at((x, ty)).walkable = walkable
|
||||
if color:
|
||||
self.grid.at((x, ty)).color = color
|
||||
if sprite is not None:
|
||||
self.grid.at((x, ty)).tilesprite = sprite
|
||||
except:
|
||||
pass
|
||||
for y in range(y1, y1+dh):
|
||||
try:
|
||||
if walkable:
|
||||
self.grid.at((tx, y)).walkable = True
|
||||
if color:
|
||||
self.grid.at((tx, y)).color = color
|
||||
if sprite is not None:
|
||||
self.grid.at((tx, y)).tilesprite = sprite
|
||||
except:
|
||||
pass
|
||||
|
||||
def generate(self, level_plan): #target_rooms = 5, features=None):
|
||||
self.reset()
|
||||
target_rooms = len(level_plan)
|
||||
if type(level_plan) is set:
|
||||
level_plan = random.choice(list(level_plan))
|
||||
while len(self.graph.walk()) < target_rooms:
|
||||
self.split(single=len(self.graph.walk()) > target_rooms * .5)
|
||||
|
||||
|
@ -152,44 +227,59 @@ class Level:
|
|||
#self.fill_rooms()
|
||||
self.wall_rooms()
|
||||
rooms = self.graph.walk()
|
||||
feature_coords = {}
|
||||
feature_coords = []
|
||||
prev_room = None
|
||||
for room in rooms:
|
||||
if not features: break
|
||||
f = features.pop(0)
|
||||
feature_coords[f] = room_coord(room, margin=4 if f in ("boulder",) else 1)
|
||||
print(level_plan)
|
||||
for room_num, room in enumerate(rooms):
|
||||
room_plan = level_plan[room_num]
|
||||
if type(room_plan) == str: room_plan = [room_plan] # single item plans became single-character plans...
|
||||
for f in room_plan:
|
||||
#feature_coords.append((f, room_coord(room, margin=4 if f in ("boulder",) else 1)))
|
||||
# boulders are breaking my brain. If I can't get boulders away from walls with margin, I'm just going to dig them out.
|
||||
if f == "boulder":
|
||||
x, y = room_coord(room, margin=0)
|
||||
if x < 2: x += 1
|
||||
if y < 2: y += 1
|
||||
if x > self.grid.grid_size[0] - 2: x -= 1
|
||||
if y > self.grid.grid_size[1] - 2: y -= 1
|
||||
for _x in (1, 0, -1):
|
||||
for _y in (1, 0, -1):
|
||||
self.grid.at((x + _x, y + _y)).walkable = True
|
||||
feature_coords.append((f, (x, y)))
|
||||
else:
|
||||
feature_coords.append((f, room_coord(room, margin=0)))
|
||||
print(feature_coords[-1])
|
||||
|
||||
## Hallway generation
|
||||
# plow an inelegant path
|
||||
if prev_room:
|
||||
start = room_coord(prev_room, margin=2)
|
||||
end = room_coord(room, margin=2)
|
||||
# get x1,y1 and x2,y2 coordinates: top left and bottom right points on the rect formed by two random points, one from each of the 2 rooms
|
||||
x1 = min([start[0], end[0]])
|
||||
x2 = max([start[0], end[0]])
|
||||
dw = x2 - x1
|
||||
y1 = min([start[1], end[1]])
|
||||
y2 = max([start[1], end[1]])
|
||||
dh = y2 - y1
|
||||
#print(x1, y1, x2, y2, dw, dh)
|
||||
|
||||
# random: top left or bottom right as the corner between the paths
|
||||
tx, ty = (x1, y1) if random.random() >= 0.5 else (x2, y2)
|
||||
|
||||
for x in range(x1, x1+dw):
|
||||
self.grid.at((x, ty)).walkable = True
|
||||
#self.grid.at((x, ty)).color = (255, 0, 0)
|
||||
#self.grid.at((x, ty)).tilesprite = -1
|
||||
for y in range(y1, y1+dh):
|
||||
self.grid.at((tx, y)).walkable = True
|
||||
#self.grid.at((tx, y)).color = (0, 255, 0)
|
||||
#self.grid.at((tx, y)).tilesprite = -1
|
||||
self.dig_path(start, end, color=(0, 64, 0))
|
||||
prev_room = room
|
||||
|
||||
|
||||
# Tile painting
|
||||
possibilities = None
|
||||
while possibilities or possibilities is None:
|
||||
possibilities = ct.wfc_pass(self.grid, possibilities)
|
||||
|
||||
## "hallway room" repainting
|
||||
#for i, hall_room in enumerate(rooms):
|
||||
# if self.walled_rooms[i]:
|
||||
# print(f"walled room: {hall_room}")
|
||||
# continue
|
||||
# print(f"hall room: {hall_room}")
|
||||
# x, y, w, h = hall_room.data
|
||||
# for _x in range(x+1, x+w-1):
|
||||
# for _y in range(y+1, y+h-1):
|
||||
# self.grid.at((_x, _y)).walkable = False
|
||||
# self.grid.at((_x, _y)).tilesprite = -1
|
||||
# self.grid.at((_x, _y)).color = (0, 0, 0) # pit!
|
||||
# targets = adjacent_rooms(hall_room, rooms)
|
||||
# print(targets)
|
||||
# for k, v in targets.items():
|
||||
# self.dig_path(hall_room.center(), v, color=(64, 32, 32))
|
||||
# for _, p in feature_coords:
|
||||
# if hall_room.contains(p): self.dig_path(hall_room.center(), p, color=(92, 48, 48))
|
||||
|
||||
return feature_coords
|
||||
|
|
|
@ -129,7 +129,7 @@ def wfc_pass(grid, possibilities=None):
|
|||
for v in possibilities.values():
|
||||
if len(v) in counts: counts[len(v)] += 1
|
||||
else: counts[len(v)] = 1
|
||||
print(counts)
|
||||
#print(counts)
|
||||
return possibilities
|
||||
elif len(possibilities) == 0:
|
||||
print("We're done!")
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import mcrfpy
|
||||
#t = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16) # 12, 11)
|
||||
#t = mcrfpy.Texture("assets/kenney_TD_MR_IP.png", 16, 16) # 12, 11)
|
||||
t = mcrfpy.Texture("assets/kenney_TD_MR_IP.png", 16, 16) # 12, 11)
|
||||
btn_tex = mcrfpy.Texture("assets/48px_ui_icons-KenneyNL.png", 48, 48)
|
||||
font = mcrfpy.Font("assets/JetbrainsMono.ttf")
|
||||
|
||||
frame_color = (64, 64, 128)
|
||||
|
@ -10,12 +11,36 @@ import cos_entities as ce
|
|||
import cos_level as cl
|
||||
#import cos_tiles as ct
|
||||
|
||||
class Resources:
|
||||
def __init__(self):
|
||||
self.music_enabled = True
|
||||
self.music_volume = 40
|
||||
self.sfx_enabled = True
|
||||
self.sfx_volume = 100
|
||||
self.master_volume = 100
|
||||
|
||||
# load the music/sfx files here
|
||||
self.splats = []
|
||||
for i in range(1, 10):
|
||||
mcrfpy.createSoundBuffer(f"assets/sfx/splat{i}.ogg")
|
||||
|
||||
|
||||
def play_sfx(self, sfx_id):
|
||||
if self.sfx_enabled and self.sfx_volume and self.master_volume:
|
||||
mcrfpy.setSoundVolume(self.master_volume/100 * self.sfx_volume)
|
||||
mcrfpy.playSound(sfx_id)
|
||||
|
||||
def play_music(self, track_id):
|
||||
if self.music_enabled and self.music_volume and self.master_volume:
|
||||
mcrfpy.setMusicVolume(self.master_volume/100 * self.music_volume)
|
||||
mcrfpy.playMusic(...)
|
||||
|
||||
resources = Resources()
|
||||
|
||||
class Crypt:
|
||||
def __init__(self):
|
||||
mcrfpy.createScene("play")
|
||||
self.ui = mcrfpy.sceneUI("play")
|
||||
mcrfpy.setScene("play")
|
||||
mcrfpy.keypressScene(self.cos_keys)
|
||||
|
||||
entity_frame = mcrfpy.Frame(815, 10, 194, 595, fill_color = frame_color)
|
||||
inventory_frame = mcrfpy.Frame(10, 610, 800, 143, fill_color = frame_color)
|
||||
|
@ -24,9 +49,66 @@ class Crypt:
|
|||
#self.level = cl.Level(30, 23)
|
||||
self.entities = []
|
||||
self.depth=1
|
||||
self.stuck_btn = SweetButton(self.ui, (810, 700), "Stuck", icon=19, box_width=150, box_height = 60, click=self.stuck)
|
||||
|
||||
self.level_plan = {
|
||||
1: [("spawn", "button", "boulder"), ("exit")],
|
||||
2: [("spawn", "button", "boulder"), ("rat"), ("exit")],
|
||||
3: [("spawn", "button", "boulder"), ("rat"), ("exit")],
|
||||
4: [("spawn", "button", "rat"), ("boulder", "rat", "treasure"), ("exit")],
|
||||
5: [("spawn", "button", "rat"), ("boulder", "rat"), ("exit")],
|
||||
6: {(("spawn", "button"), ("boulder", "treasure", "exit")),
|
||||
(("spawn", "boulder"), ("button", "treasure", "exit"))},
|
||||
7: {(("spawn", "button"), ("boulder", "treasure", "exit")),
|
||||
(("spawn", "boulder"), ("button", "treasure", "exit"))},
|
||||
8: {(("spawn", "treasure", "button"), ("boulder", "treasure", "exit")),
|
||||
(("spawn", "treasure", "boulder"), ("button", "treasure", "exit"))}
|
||||
#9: self.lv_planner
|
||||
}
|
||||
|
||||
# empty void for the player to initialize into
|
||||
self.headsup = mcrfpy.Frame(10, 684, 320, 64, fill_color = (0, 0, 0, 0))
|
||||
self.sidebar = mcrfpy.Frame(860, 4, 160, 600, fill_color = (96, 96, 160))
|
||||
|
||||
# Heads Up (health bar, armor bar) config
|
||||
self.health_bar = [mcrfpy.Sprite(32*i, 2, t, 659, 2) for i in range(10)]
|
||||
[self.headsup.children.append(i) for i in self.health_bar]
|
||||
self.armor_bar = [mcrfpy.Sprite(32*i, 42, t, 659, 2) for i in range(10)]
|
||||
[self.headsup.children.append(i) for i in self.armor_bar]
|
||||
# (40, 3), caption, font, fill_color=font_color
|
||||
self.stat_captions = mcrfpy.Caption((325,0), "HP:10\nDef:0(+0)", font, fill_color=(255, 255, 255))
|
||||
self.stat_captions.outline = 3
|
||||
self.stat_captions.outline_color = (0, 0, 0)
|
||||
self.headsup.children.append(self.stat_captions)
|
||||
|
||||
# Side Bar (inventory, level info) config
|
||||
self.level_caption = mcrfpy.Caption((5,5), "Level: 1", font, fill_color=(255, 255, 255))
|
||||
self.level_caption.size = 26
|
||||
self.level_caption.outline = 3
|
||||
self.level_caption.outline_color = (0, 0, 0)
|
||||
self.sidebar.children.append(self.level_caption)
|
||||
self.inv_sprites = [mcrfpy.Sprite(15, 70 + 95*i, t, 659, 6.0) for i in range(5)]
|
||||
for i in self.inv_sprites:
|
||||
self.sidebar.children.append(i)
|
||||
self.key_captions = [
|
||||
mcrfpy.Sprite(75, 130 + (95*2) + 95 * i, t, 384 + i, 3.0) for i in range(3)
|
||||
]
|
||||
for i in self.key_captions:
|
||||
self.sidebar.children.append(i)
|
||||
self.inv_captions = [
|
||||
mcrfpy.Caption((25, 130 + 95 * i), "x", font, fill_color=(255, 255, 255)) for i in range(5)
|
||||
]
|
||||
for i in self.inv_captions:
|
||||
self.sidebar.children.append(i)
|
||||
|
||||
liminal_void = mcrfpy.Grid(1, 1, t, (0, 0), (16, 16))
|
||||
self.grid = liminal_void
|
||||
self.player = ce.PlayerEntity(game=self)
|
||||
self.spawn_point = (0, 0)
|
||||
|
||||
# level creation moves player to the game level at the generated spawn point
|
||||
self.create_level(self.depth)
|
||||
#self.grid = mcrfpy.Grid(20, 15, t, (10, 10), (1014, 758))
|
||||
self.player = ce.PlayerEntity(game=self)
|
||||
self.swap_level(self.level, self.spawn_point)
|
||||
|
||||
# Test Entities
|
||||
|
@ -35,10 +117,146 @@ class Crypt:
|
|||
#ce.ExitEntity(12, 6, 14, 4, game=self)
|
||||
# scene setup
|
||||
|
||||
|
||||
[self.ui.append(e) for e in (self.grid,)] # entity_frame, inventory_frame, stats_frame)]
|
||||
## might be done by self.swap_level
|
||||
#[self.ui.append(e) for e in (self.grid, self.stuck_btn.base_frame)] # entity_frame, inventory_frame, stats_frame)]
|
||||
|
||||
self.possibilities = None # track WFC possibilities between rounds
|
||||
self.enemies = []
|
||||
#mcrfpy.setTimer("enemy_test", self.enemy_movement, 750)
|
||||
|
||||
#mcrfpy.Frame(x, y, box_width+shadow_offset, box_height, fill_color = (0, 0, 0, 255))
|
||||
#Sprite(0, 3, btn_tex, icon, icon_scale)
|
||||
|
||||
#def enemy_movement(self, *args):
|
||||
# for e in self.enemies: e.act()
|
||||
|
||||
#def spawn_test_rat(self):
|
||||
# success = False
|
||||
# while not success:
|
||||
# x, y = [random.randint(0, i-1) for i in self.grid.grid_size]
|
||||
# success = self.grid.at((x,y)).walkable
|
||||
# self.enemies.append(ce.EnemyEntity(x, y, game=self))
|
||||
|
||||
def gui_update(self):
|
||||
self.stat_captions.text = f"HP:{self.player.hp}\nDef:{self.player.calc_defense()}(+{self.player.calc_defense() - self.player.base_defense})"
|
||||
for i, hs in enumerate(self.health_bar):
|
||||
full_hearts = self.player.hp - (i*2)
|
||||
empty_hearts = self.player.max_hp - (i*2)
|
||||
hs.sprite_number = 659
|
||||
if empty_hearts >= 2:
|
||||
hs.sprite_number = 208
|
||||
if full_hearts >= 2:
|
||||
hs.sprite_number = 210
|
||||
elif full_hearts == 1:
|
||||
hs.sprite_number = 209
|
||||
for i, arm_s in enumerate(self.armor_bar):
|
||||
full_hearts = self.player.calc_defense() - (i*2)
|
||||
arm_s.sprite_number = 659
|
||||
if full_hearts >= 2:
|
||||
arm_s.sprite_number = 211
|
||||
elif full_hearts == 1:
|
||||
arm_s.sprite_number = 212
|
||||
|
||||
#items = self.player.equipped[:] + self.player.inventory[:]
|
||||
for i in range(5):
|
||||
if i == 0:
|
||||
item = self.player.equipped[0] if len(self.player.equipped) > 0 else None
|
||||
elif i == 1:
|
||||
item = self.player.equipped[1] if len(self.player.equipped) > 1 else None
|
||||
elif i == 2:
|
||||
item = self.player.inventory[0] if len(self.player.inventory) > 0 else None
|
||||
elif i == 3:
|
||||
item = self.player.inventory[1] if len(self.player.inventory) > 1 else None
|
||||
elif i == 4:
|
||||
item = self.player.inventory[2] if len(self.player.inventory) > 2 else None
|
||||
if item is None:
|
||||
self.inv_sprites[i].sprite_number = 659
|
||||
if i > 1: self.key_captions[i - 2].sprite_number = 659
|
||||
self.inv_captions[i].text = ""
|
||||
continue
|
||||
self.inv_sprites[i].sprite_number = item.sprite
|
||||
if i > 1:
|
||||
self.key_captions[i - 2].sprite_number = 384 + (i - 2)
|
||||
self.inv_captions[i].text = "Blah"
|
||||
|
||||
def lv_planner(self, target_level):
|
||||
"""Plan room sequence in levels > 9"""
|
||||
monsters = (target_level - 6) // 2
|
||||
target_rooms = min(int(target_level // 2), 6)
|
||||
target_treasure = min(int(target_level // 3), 4)
|
||||
rooms = []
|
||||
for i in range(target_rooms):
|
||||
rooms.append([])
|
||||
for o in ("spawn", "boulder", "button", "exit"):
|
||||
r = random.randint(0, target_rooms-1)
|
||||
rooms[r].append(o)
|
||||
monster_table = {
|
||||
"rat": int(monsters * 0.8) + 2,
|
||||
"big rat": max(int(monsters * 0.2) - 2, 0),
|
||||
"cyclops": max(int(monsters * 0.1) - 3, 0)
|
||||
}
|
||||
monster_table = {k: v for k, v in monster_table.items() if v > 0}
|
||||
monster_names = list(monster_table.keys())
|
||||
monster_weights = [monster_table[k] for k in monster_names]
|
||||
for m in range(monsters):
|
||||
r = random.randint(0, target_rooms - 1)
|
||||
rooms[r].append(random.choices(monster_names, weights = monster_weights)[0])
|
||||
|
||||
for t in range(target_treasure):
|
||||
r = random.randint(0, target_rooms - 1)
|
||||
rooms[r].append("treasure")
|
||||
|
||||
return rooms
|
||||
|
||||
def treasure_planner(self, treasure_level):
|
||||
"""Plan treasure contents at all levels"""
|
||||
item_minlv = {
|
||||
"buckler": 1,
|
||||
"shield": 2,
|
||||
"sword": 1,
|
||||
"sword2": 2,
|
||||
"sword3": 5,
|
||||
"axe": 1,
|
||||
"axe2": 2,
|
||||
"axe3": 5,
|
||||
"wand": 1,
|
||||
"staff": 2,
|
||||
"staff2": 5,
|
||||
"red_pot": 3,
|
||||
"blue_pot": 6,
|
||||
"green_pot": 6,
|
||||
"grey_pot": 6,
|
||||
"sm_grey_pot": 1
|
||||
}
|
||||
base_wts = {
|
||||
("buckler", "shield"): 0.25, # defensive items
|
||||
("sword", "sword2", "axe", "axe2"): 0.4, #1h weapons
|
||||
("sword3", "axe3"): 0.25, #2h weapons
|
||||
("wand", "staff", "staff2"): 0.15, #magic weapons
|
||||
("red_pot",): 0.25, #health
|
||||
("blue_pot", "green_pot", "grey_pot", "sm_grey_pot"): 0.2 #stat upgrade potions
|
||||
}
|
||||
|
||||
# find item name in base_wts key (base weight of the category)
|
||||
base_weight = lambda s: base_wts[list([t for t in base_wts.keys() if s in t])[0]]
|
||||
weights = {d[0]: base_weight(d[0]) for d in item_minlv.items() if treasure_level > d[1]}
|
||||
if self.player.archetype is None:
|
||||
prefs = []
|
||||
elif self.player.archetype == "viking":
|
||||
prefs = ["axe2", "axe3", "green_pot"]
|
||||
elif self.player.archetype == "knight":
|
||||
prefs = ["sword2", "shield", "grey_pot"]
|
||||
elif self.player.archetype == "wizard":
|
||||
prefs = ["staff", "staff2", "blue_pot"]
|
||||
for i in prefs:
|
||||
if i in weights: weights[i] *= 3
|
||||
|
||||
return weights
|
||||
|
||||
def start(self):
|
||||
resources.play_sfx(1)
|
||||
mcrfpy.setScene("play")
|
||||
mcrfpy.keypressScene(self.cos_keys)
|
||||
|
||||
def add_entity(self, e:ce.COSEntity):
|
||||
self.entities.append(e)
|
||||
|
@ -49,22 +267,52 @@ class Crypt:
|
|||
for e in self.entities:
|
||||
self.grid.entities.append(e._entity)
|
||||
|
||||
def create_level(self, depth):
|
||||
def create_level(self, depth, _luck = 0):
|
||||
#if depth < 3:
|
||||
# features = None
|
||||
self.level = cl.Level(30, 23)
|
||||
self.level = cl.Level(20, 20)
|
||||
self.grid = self.level.grid
|
||||
coords = self.level.generate()
|
||||
if depth in self.level_plan:
|
||||
plan = self.level_plan[depth]
|
||||
else:
|
||||
plan = self.lv_planner(depth)
|
||||
coords = self.level.generate(plan)
|
||||
self.entities = []
|
||||
for k, v in coords.items():
|
||||
if self.player:
|
||||
luck = self.player.luck
|
||||
else:
|
||||
luck = 0
|
||||
buttons = []
|
||||
for k, v in sorted(coords, key=lambda i: i[0]): # "button" before "exit"; "button", "button", "door", "exit" -> alphabetical is correct sequence
|
||||
if k == "spawn":
|
||||
self.spawn_point = v
|
||||
if self.player:
|
||||
self.add_entity(self.player)
|
||||
#self.player.draw_pos = v
|
||||
self.spawn_point = v
|
||||
elif k == "boulder":
|
||||
ce.BoulderEntity(v[0], v[1], game=self)
|
||||
elif k == "treasure":
|
||||
ce.TreasureEntity(v[0], v[1], treasure_table = self.treasure_planner(depth + luck), game=self)
|
||||
elif k == "button":
|
||||
pass
|
||||
buttons.append(v)
|
||||
elif k == "exit":
|
||||
ce.ExitEntity(v[0], v[1], coords["button"][0], coords["button"][1], game=self)
|
||||
btn = buttons.pop(0)
|
||||
ce.ExitEntity(v[0], v[1], btn[0], btn[1], game=self)
|
||||
elif k == "rat":
|
||||
ce.EnemyEntity(*v, game=self)
|
||||
elif k == "big rat":
|
||||
ce.EnemyEntity(*v, game=self, base_damage=2, hp=4, sprite=130)
|
||||
elif k == "cyclops":
|
||||
ce.EnemyEntity(*v, game=self, base_damage=3, hp=8, sprite=109, base_defense=2)
|
||||
|
||||
#if self.depth > 2:
|
||||
#for i in range(10):
|
||||
# self.spawn_test_rat()
|
||||
|
||||
def stuck(self, sweet_btn, args):
|
||||
if args[3] == "end": return
|
||||
self.create_level(self.depth)
|
||||
self.swap_level(self.level, self.spawn_point)
|
||||
|
||||
def cos_keys(self, key, state):
|
||||
d = None
|
||||
|
@ -73,37 +321,286 @@ class Crypt:
|
|||
elif key == "A": d = (-1, 0)
|
||||
elif key == "S": d = (0, 1)
|
||||
elif key == "D": d = (1, 0)
|
||||
elif key == "M": self.level.generate()
|
||||
elif key == "R":
|
||||
self.level.reset()
|
||||
self.possibilities = None
|
||||
elif key == "T":
|
||||
self.level.split()
|
||||
self.possibilities = None
|
||||
elif key == "Y": self.level.split(single=True)
|
||||
elif key == "U": self.level.highlight(+1)
|
||||
elif key == "I": self.level.highlight(-1)
|
||||
elif key == "O":
|
||||
self.level.wall_rooms()
|
||||
self.possibilities = None
|
||||
#elif key == "M": self.level.generate()
|
||||
#elif key == "R":
|
||||
# self.level.reset()
|
||||
# self.possibilities = None
|
||||
#elif key == "T":
|
||||
# self.level.split()
|
||||
# self.possibilities = None
|
||||
#elif key == "Y": self.level.split(single=True)
|
||||
#elif key == "U": self.level.highlight(+1)
|
||||
#elif key == "I": self.level.highlight(-1)
|
||||
#elif key == "O":
|
||||
# self.level.wall_rooms()
|
||||
# self.possibilities = None
|
||||
#elif key == "P": ct.format_tiles(self.grid)
|
||||
#elif key == "P":
|
||||
#self.possibilities = ct.wfc_pass(self.grid, self.possibilities)
|
||||
elif key == "P":
|
||||
self.possibilities = ct.wfc_pass(self.grid, self.possibilities)
|
||||
if d: self.player.try_move(*d)
|
||||
self.depth += 1
|
||||
print(f"Descending: lv {self.depth}")
|
||||
self.stuck(None, [1,2,3,4])
|
||||
elif key == "Period":
|
||||
self.enemy_turn()
|
||||
elif key == "X":
|
||||
self.pull_boulder_search()
|
||||
#else:
|
||||
# print(key)
|
||||
if d:
|
||||
self.entities.sort(key = lambda e: e.draw_order, reverse=False)
|
||||
self.player.try_move(*d)
|
||||
self.enemy_turn()
|
||||
|
||||
def enemy_turn(self):
|
||||
self.entities.sort(key = lambda e: e.draw_order, reverse=False)
|
||||
for e in self.entities:
|
||||
e.act()
|
||||
self.gui_update()
|
||||
|
||||
def pull_boulder_search(self):
|
||||
for dx, dy in ( (0, -1), (-1, 0), (1, 0), (0, 1) ):
|
||||
for e in self.entities:
|
||||
if e.draw_pos != (self.player.draw_pos[0] + dx, self.player.draw_pos[1] + dy): continue
|
||||
if type(e) == ce.BoulderEntity:
|
||||
self.pull_boulder_move((dx, dy), e)
|
||||
return self.enemy_turn()
|
||||
else:
|
||||
print("No boulder found to pull.")
|
||||
|
||||
def pull_boulder_move(self, p, target_boulder):
|
||||
print(p, target_boulder)
|
||||
self.entities.sort(key = lambda e: e.draw_order, reverse=False)
|
||||
if self.player.try_move(-p[0], -p[1], test=True):
|
||||
old_pos = self.player.draw_pos
|
||||
self.player.try_move(-p[0], -p[1])
|
||||
target_boulder.do_move(*old_pos)
|
||||
|
||||
def swap_level(self, new_level, spawn_point):
|
||||
self.level = new_level
|
||||
self.grid = self.level.grid
|
||||
self.grid.zoom = 2.0
|
||||
# TODO, make an entity mover function
|
||||
self.add_entity(self.player)
|
||||
#self.add_entity(self.player)
|
||||
self.player.grid = self.grid
|
||||
self.player.draw_pos = spawn_point
|
||||
self.grid.entities.append(self.player._entity)
|
||||
try:
|
||||
self.ui.remove(0)
|
||||
except:
|
||||
pass
|
||||
self.ui.append(self.grid)
|
||||
#self.grid.entities.append(self.player._entity)
|
||||
|
||||
crypt = Crypt()
|
||||
# reform UI (workaround to ui collection iterators crashing)
|
||||
while len(self.ui) > 0:
|
||||
try:
|
||||
self.ui.remove(0)
|
||||
except:
|
||||
pass
|
||||
self.ui.append(self.grid)
|
||||
self.ui.append(self.stuck_btn.base_frame)
|
||||
self.ui.append(self.headsup)
|
||||
|
||||
self.level_caption.text = f"Level: {self.depth}"
|
||||
self.ui.append(self.sidebar)
|
||||
self.gui_update()
|
||||
|
||||
class SweetButton:
|
||||
def __init__(self, ui:mcrfpy.UICollection,
|
||||
pos:"Tuple[int, int]",
|
||||
caption:str, font=font, font_size=24, font_color=(255,255,255), font_outline_color=(0, 0, 0), font_outline_width=2,
|
||||
shadow_offset = 8, box_width=200, box_height = 80, shadow_color=(64, 64, 86), box_color=(96, 96, 160),
|
||||
icon=4, icon_scale=1.75, click=lambda *args: None):
|
||||
self.ui = ui
|
||||
self.shadow_box = mcrfpy.Frame
|
||||
x, y = pos
|
||||
|
||||
# box w/ drop shadow
|
||||
self.shadow_offset = shadow_offset
|
||||
self.base_frame = mcrfpy.Frame(x, y, box_width+shadow_offset, box_height, fill_color = (0, 0, 0, 255))
|
||||
self.base_frame.click = self.do_click
|
||||
|
||||
# drop shadow won't need configured, append directly
|
||||
self.base_frame.children.append(mcrfpy.Frame(0, 0, box_width, box_height, fill_color = shadow_color))
|
||||
|
||||
# main button is where the content lives
|
||||
self.main_button = mcrfpy.Frame(shadow_offset, shadow_offset, box_width, box_height, fill_color = box_color)
|
||||
self.click = click
|
||||
self.base_frame.children.append(self.main_button)
|
||||
|
||||
# main button icon
|
||||
self.icon = mcrfpy.Sprite(0, 3, btn_tex, icon, icon_scale)
|
||||
self.main_button.children.append(self.icon)
|
||||
|
||||
# main button caption
|
||||
self.caption = mcrfpy.Caption((40, 3), caption, font, fill_color=font_color)
|
||||
self.caption.size = font_size
|
||||
self.caption.outline_color=font_outline_color
|
||||
self.caption.outline=font_outline_width
|
||||
self.main_button.children.append(self.caption)
|
||||
|
||||
def unpress(self):
|
||||
"""Helper func for when graphics changes or glitches make the button stuck down"""
|
||||
self.main_button.x, self.main_button.y = (self.shadow_offset, self.shadow_offset)
|
||||
|
||||
def do_click(self, x, y, mousebtn, event):
|
||||
if event == "start":
|
||||
self.main_button.x, self.main_button.y = (0, 0)
|
||||
elif event == "end":
|
||||
self.main_button.x, self.main_button.y = (self.shadow_offset, self.shadow_offset)
|
||||
result = self.click(self, (x, y, mousebtn, event))
|
||||
if result: # return True from event function to instantly un-pop
|
||||
self.main_button.x, self.main_button.y = (self.shadow_offset, self.shadow_offset)
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
return self.caption.text
|
||||
|
||||
@text.setter
|
||||
def text(self, value):
|
||||
self.caption.text = value
|
||||
|
||||
@property
|
||||
def sprite_number(self):
|
||||
return self.icon.sprite_number
|
||||
|
||||
@sprite_number.setter
|
||||
def sprite_number(self, value):
|
||||
self.icon.sprite_number = value
|
||||
|
||||
class MainMenu:
|
||||
def __init__(self):
|
||||
mcrfpy.createScene("menu")
|
||||
self.ui = mcrfpy.sceneUI("menu")
|
||||
mcrfpy.setScene("menu")
|
||||
self.crypt = None
|
||||
|
||||
components = []
|
||||
# demo grid
|
||||
#components.append(
|
||||
# )
|
||||
|
||||
# title text
|
||||
drop_shadow = mcrfpy.Caption((150, 10), "Crypt Of Sokoban", font, fill_color=(96, 96, 96), outline_color=(192, 0, 0))
|
||||
drop_shadow.outline = 3
|
||||
drop_shadow.size = 64
|
||||
components.append(
|
||||
drop_shadow
|
||||
)
|
||||
|
||||
title_txt = mcrfpy.Caption((158, 18), "Crypt Of Sokoban", font, fill_color=(255, 255, 255))
|
||||
title_txt.size = 64
|
||||
components.append(
|
||||
title_txt
|
||||
)
|
||||
|
||||
# toast: text over the demo grid that fades out on a timer
|
||||
self.toast = mcrfpy.Caption((150, 400), "", font, fill_color=(0, 0, 0))
|
||||
self.toast.size = 28
|
||||
self.toast.outline = 2
|
||||
self.toast.outline_color = (255, 255, 255)
|
||||
self.toast_event = None
|
||||
components.append(self.toast)
|
||||
|
||||
# button - PLAY
|
||||
#playbtn = mcrfpy.Frame(284, 548, 456, 120, fill_color =
|
||||
play_btn = SweetButton(self.ui, (284, 548), "PLAY", box_width=456, box_height=110, icon=1, icon_scale=2.0, click=self.play)
|
||||
components.append(play_btn.base_frame)
|
||||
|
||||
# button - config menu pane
|
||||
#self.config = lambda self, sweet_btn, *args: print(f"boop, sweet button {sweet_btn} config {args}")
|
||||
config_btn = SweetButton(self.ui, (10, 678), "Settings", icon=2, click=self.show_config)
|
||||
components.append(config_btn.base_frame)
|
||||
|
||||
# button - insta-1080p scaling
|
||||
scale_btn = SweetButton(self.ui, (10+256, 678), "Scale up\nto 1080p", icon=15, click=self.scale)
|
||||
self.scaled = False
|
||||
components.append(scale_btn.base_frame)
|
||||
|
||||
# button - music toggle
|
||||
music_btn = SweetButton(self.ui, (10+256*2, 678), "Music\nON", icon=12, click=self.music_toggle)
|
||||
self.music_enabled = True
|
||||
self.music_volume = 40
|
||||
components.append(music_btn.base_frame)
|
||||
|
||||
# button - sfx toggle
|
||||
sfx_btn = SweetButton(self.ui, (10+256*3, 678), "SFX\nON", icon=0, click=self.sfx_toggle)
|
||||
self.sfx_enabled = True
|
||||
self.sfx_volume = 40
|
||||
components.append(sfx_btn.base_frame)
|
||||
|
||||
[self.ui.append(e) for e in components]
|
||||
|
||||
def toast_say(self, txt, delay=10):
|
||||
"kick off a toast event"
|
||||
if self.toast_event is not None:
|
||||
mcrfpy.delTimer("toast_timer")
|
||||
self.toast.text = txt
|
||||
self.toast_event = 350
|
||||
self.toast.fill_color = (255, 255, 255, 255)
|
||||
self.toast.outline = 2
|
||||
self.toast.outline_color = (0, 0, 0, 255)
|
||||
mcrfpy.setTimer("toast_timer", self.toast_callback, 100)
|
||||
|
||||
def toast_callback(self, *args):
|
||||
"fade out the toast text"
|
||||
self.toast_event -= 5
|
||||
if self.toast_event < 0:
|
||||
self.toast_event = None
|
||||
mcrfpy.delTimer("toast_timer")
|
||||
mcrfpy.text = ""
|
||||
return
|
||||
a = min(self.toast_event, 255)
|
||||
self.toast.fill_color = (255, 255, 255, a)
|
||||
self.toast.outline_color = (0, 0, 0, a)
|
||||
|
||||
def show_config(self, sweet_btn, args):
|
||||
self.toast_say("Beep, Boop! Configurations will go here.")
|
||||
|
||||
def play(self, sweet_btn, args):
|
||||
#if args[3] == "start": return # DRAMATIC on release action!
|
||||
if args[3] == "end": return
|
||||
self.crypt = Crypt()
|
||||
#mcrfpy.setScene("play")
|
||||
self.crypt.start()
|
||||
|
||||
def scale(self, sweet_btn, args, window_scale=None):
|
||||
if args[3] == "end": return
|
||||
if not window_scale:
|
||||
self.scaled = not self.scaled
|
||||
window_scale = 1.3
|
||||
else:
|
||||
self.scaled = True
|
||||
sweet_btn.unpress()
|
||||
if self.scaled:
|
||||
self.toast_say("Windowed mode only, sorry!\nCheck Settings for for fine-tuned controls.")
|
||||
mcrfpy.setScale(window_scale)
|
||||
sweet_btn.text = "Scale down\n to 1.0x"
|
||||
else:
|
||||
mcrfpy.setScale(1.0)
|
||||
sweet_btn.text = "Scale up\nto 1080p"
|
||||
|
||||
def music_toggle(self, sweet_btn, args):
|
||||
if args[3] == "end": return
|
||||
self.music_enabled = not self.music_enabled
|
||||
print(f"music: {self.music_enabled}")
|
||||
if self.music_enabled:
|
||||
mcrfpy.setMusicVolume(self.music_volume)
|
||||
sweet_btn.text = "Music is ON"
|
||||
sweet_btn.sprite_number = 12
|
||||
else:
|
||||
self.toast_say("Use your volume keys or\nlook in Settings for a volume meter.")
|
||||
mcrfpy.setMusicVolume(0)
|
||||
sweet_btn.text = "Music is OFF"
|
||||
sweet_btn.sprite_number = 17
|
||||
|
||||
def sfx_toggle(self, sweet_btn, args):
|
||||
if args[3] == "end": return
|
||||
self.sfx_enabled = not self.sfx_enabled
|
||||
print(f"sfx: {self.sfx_enabled}")
|
||||
if self.sfx_enabled:
|
||||
mcrfpy.setSoundVolume(self.sfx_volume)
|
||||
sweet_btn.text = "SFX are ON"
|
||||
sweet_btn.sprite_number = 0
|
||||
else:
|
||||
self.toast_say("Use your volume keys or\nlook in Settings for a volume meter.")
|
||||
mcrfpy.setSoundVolume(0)
|
||||
sweet_btn.text = "SFX are OFF"
|
||||
sweet_btn.sprite_number = 17
|
||||
|
||||
mainmenu = MainMenu()
|
||||
|
|
Loading…
Reference in New Issue