7DRL 2025 progress
This commit is contained in:
parent
e928dda4b3
commit
6be474da08
16 changed files with 856 additions and 80 deletions
Binary file not shown.
|
Before Width: | Height: | Size: 662 KiB After Width: | Height: | Size: 674 KiB |
BIN
assets/sfx/splat1.ogg
Normal file
BIN
assets/sfx/splat1.ogg
Normal file
Binary file not shown.
BIN
assets/sfx/splat2.ogg
Normal file
BIN
assets/sfx/splat2.ogg
Normal file
Binary file not shown.
BIN
assets/sfx/splat3.ogg
Normal file
BIN
assets/sfx/splat3.ogg
Normal file
Binary file not shown.
BIN
assets/sfx/splat4.ogg
Normal file
BIN
assets/sfx/splat4.ogg
Normal file
Binary file not shown.
BIN
assets/sfx/splat5.ogg
Normal file
BIN
assets/sfx/splat5.ogg
Normal file
Binary file not shown.
BIN
assets/sfx/splat6.ogg
Normal file
BIN
assets/sfx/splat6.ogg
Normal file
Binary file not shown.
BIN
assets/sfx/splat7.ogg
Normal file
BIN
assets/sfx/splat7.ogg
Normal file
Binary file not shown.
BIN
assets/sfx/splat8.ogg
Normal file
BIN
assets/sfx/splat8.ogg
Normal file
Binary file not shown.
BIN
assets/sfx/splat9.ogg
Normal file
BIN
assets/sfx/splat9.ogg
Normal file
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)
|
||||
|
|
@ -70,6 +71,8 @@ class COSEntity(): #mcrfpy.Entity): # Fake mcrfpy.Entity integration; engine bu
|
|||
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,32 +181,8 @@ 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
|
||||
self.reset()
|
||||
while len(self.graph.walk()) < target_rooms:
|
||||
self.split(single=len(self.graph.walk()) > target_rooms * .5)
|
||||
|
||||
# Player path planning
|
||||
#self.fill_rooms()
|
||||
self.wall_rooms()
|
||||
rooms = self.graph.walk()
|
||||
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)
|
||||
|
||||
## Hallway generation
|
||||
# plow an inelegant path
|
||||
if prev_room:
|
||||
start = room_coord(prev_room, margin=2)
|
||||
end = room_coord(room, margin=2)
|
||||
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]])
|
||||
|
|
@ -171,25 +190,96 @@ class Level:
|
|||
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
|
||||
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
|
||||
#self.grid.at((tx, y)).color = (0, 255, 0)
|
||||
#self.grid.at((tx, y)).tilesprite = -1
|
||||
prev_room = room
|
||||
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)
|
||||
|
||||
# Player path planning
|
||||
#self.fill_rooms()
|
||||
self.wall_rooms()
|
||||
rooms = self.graph.walk()
|
||||
feature_coords = []
|
||||
prev_room = None
|
||||
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)
|
||||
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":
|
||||
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)
|
||||
#self.grid.entities.append(self.player._entity)
|
||||
|
||||
# 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)
|
||||
|
||||
crypt = Crypt()
|
||||
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…
Add table
Add a link
Reference in a new issue