feat: Initial commit for GrimHold with requirements and INSTALL.md
This commit is contained in:
+196
@@ -0,0 +1,196 @@
|
||||
import time
|
||||
import math
|
||||
import jwt
|
||||
import bcrypt
|
||||
from .database import get_connection
|
||||
from .game_data import BUILDING_DEFINITIONS, GRID_SIZES, STORAGE_CAPACITIES
|
||||
|
||||
SECRET_KEY = "grimhold_dark_fantasy_2026_super_secret_jwt"
|
||||
JWT_ALGORITHM = "HS256"
|
||||
|
||||
def hash_password(password: str) -> str:
|
||||
return bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()).decode("utf-8")
|
||||
|
||||
def verify_password(plain: str, hashed: str) -> bool:
|
||||
return bcrypt.checkpw(plain.encode("utf-8"), hashed.encode("utf-8"))
|
||||
|
||||
def create_access_token(user_id: int, username: str) -> str:
|
||||
payload = {
|
||||
"sub": str(user_id),
|
||||
"username": username,
|
||||
"exp": time.time() + 86400 * 14 # 14 days
|
||||
}
|
||||
return jwt.encode(payload, SECRET_KEY, algorithm=JWT_ALGORITHM)
|
||||
|
||||
def decode_access_token(token: str) -> dict:
|
||||
try:
|
||||
return jwt.decode(token, SECRET_KEY, algorithms=[JWT_ALGORITHM])
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def calculate_costs(b_type: str, level: int):
|
||||
defn = BUILDING_DEFINITIONS.get(b_type)
|
||||
if not defn:
|
||||
return None, 10
|
||||
mult = defn["cost_multiplier"] ** (level - 1)
|
||||
t_mult = defn["time_multiplier"] ** (level - 1)
|
||||
|
||||
costs = {
|
||||
"wood": math.floor(defn["base_cost"]["wood"] * mult),
|
||||
"iron": math.floor(defn["base_cost"]["iron"] * mult),
|
||||
"food": math.floor(defn["base_cost"]["food"] * mult),
|
||||
"essence": math.floor(defn["base_cost"]["essence"] * mult),
|
||||
}
|
||||
duration = math.floor(defn["base_time"] * t_mult)
|
||||
return costs, duration
|
||||
|
||||
def update_player_state(conn, user_id: int):
|
||||
now = time.time()
|
||||
|
||||
# 1. Fetch user & buildings & queues
|
||||
cur = conn.cursor()
|
||||
cur.execute("SELECT * FROM users WHERE id = ?", (user_id,))
|
||||
user = cur.fetchone()
|
||||
if not user:
|
||||
return None
|
||||
|
||||
cur.execute("SELECT * FROM resources WHERE user_id = ?", (user_id,))
|
||||
res = cur.fetchone()
|
||||
|
||||
# 2. Check and finish building queue items
|
||||
cur.execute("SELECT * FROM building_queue WHERE user_id = ? AND finishes_at <= ? ORDER BY finishes_at ASC", (user_id, now))
|
||||
finished_items = cur.fetchall()
|
||||
|
||||
fortress_lvl = user["fortress_level"]
|
||||
grid_w = user["grid_w"]
|
||||
grid_h = user["grid_h"]
|
||||
max_storage = res["max_storage"]
|
||||
|
||||
for item in finished_items:
|
||||
b_type = item["b_type"]
|
||||
target_lvl = item["target_level"]
|
||||
defn = BUILDING_DEFINITIONS.get(b_type, {})
|
||||
new_hp = defn.get("base_hp", 100) + (target_lvl - 1) * defn.get("hp_per_lvl", 50)
|
||||
|
||||
if item["building_id"]:
|
||||
# Upgrade existing
|
||||
cur.execute("""
|
||||
UPDATE buildings
|
||||
SET level = ?, max_hp = ?, hp = ?
|
||||
WHERE id = ? AND user_id = ?
|
||||
""", (target_lvl, new_hp, new_hp, item["building_id"], user_id))
|
||||
else:
|
||||
# New placement
|
||||
cur.execute("""
|
||||
INSERT INTO buildings (user_id, b_type, level, x, y, hp, max_hp, status)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, 'active')
|
||||
""", (user_id, b_type, target_lvl, item["x"], item["y"], new_hp, new_hp))
|
||||
|
||||
# Core building upgrade expands the whole sanctuary!
|
||||
if b_type == "core":
|
||||
fortress_lvl = target_lvl
|
||||
new_size = GRID_SIZES.get(fortress_lvl, {"w": 16 + (fortress_lvl-1)*4, "h": 10 + (fortress_lvl-1)*2})
|
||||
grid_w = new_size["w"]
|
||||
grid_h = new_size["h"]
|
||||
max_storage = STORAGE_CAPACITIES.get(fortress_lvl, 2000 * fortress_lvl)
|
||||
cur.execute("""
|
||||
UPDATE users
|
||||
SET fortress_level = ?, grid_w = ?, grid_h = ?
|
||||
WHERE id = ?
|
||||
""", (fortress_lvl, grid_w, grid_h, user_id))
|
||||
cur.execute("UPDATE resources SET max_storage = ? WHERE user_id = ?", (max_storage, user_id))
|
||||
|
||||
cur.execute("DELETE FROM building_queue WHERE id = ?", (item["id"],))
|
||||
|
||||
# 3. Calculate production rates from all active economy buildings
|
||||
cur.execute("SELECT * FROM buildings WHERE user_id = ? AND status = 'active'", (user_id,))
|
||||
all_b = cur.fetchall()
|
||||
|
||||
wood_rate = 5.0 / 60.0 # base 5/min passive
|
||||
iron_rate = 3.0 / 60.0 # base 3/min passive
|
||||
food_rate = 5.0 / 60.0 # base 5/min passive
|
||||
essence_rate = 0.5 / 60.0 # base 0.5/min passive
|
||||
|
||||
for b in all_b:
|
||||
b_type = b["b_type"]
|
||||
lvl = b["level"]
|
||||
defn = BUILDING_DEFINITIONS.get(b_type)
|
||||
if not defn or "base_rate" not in defn:
|
||||
continue
|
||||
rate = defn["base_rate"] + (lvl - 1) * defn.get("rate_per_lvl", 0)
|
||||
if b_type == "woodcutter":
|
||||
wood_rate += rate
|
||||
elif b_type == "mine":
|
||||
iron_rate += rate
|
||||
elif b_type == "farm":
|
||||
food_rate += rate
|
||||
|
||||
# 4. Calculate resource delta
|
||||
dt = max(0.0, now - user["last_tick_at"])
|
||||
new_wood = min(max_storage, res["wood"] + dt * wood_rate)
|
||||
new_iron = min(max_storage, res["iron"] + dt * iron_rate)
|
||||
new_food = min(max_storage, res["food"] + dt * food_rate)
|
||||
new_essence = min(max_storage, res["essence"] + dt * essence_rate)
|
||||
|
||||
cur.execute("""
|
||||
UPDATE resources
|
||||
SET wood = ?, iron = ?, food = ?, essence = ?
|
||||
WHERE user_id = ?
|
||||
""", (new_wood, new_iron, new_food, new_essence, user_id))
|
||||
cur.execute("UPDATE users SET last_tick_at = ? WHERE id = ?", (now, user_id))
|
||||
|
||||
return {
|
||||
"rates": {
|
||||
"wood_min": round(wood_rate * 60, 1),
|
||||
"iron_min": round(iron_rate * 60, 1),
|
||||
"food_min": round(food_rate * 60, 1),
|
||||
"essence_min": round(essence_rate * 60, 1),
|
||||
}
|
||||
}
|
||||
|
||||
def create_starter_fortress(conn, user_id: int):
|
||||
# Places core treasury at x=12, y=4 (for 16x10 start)
|
||||
# A few starter walls and basic economy
|
||||
now = time.time()
|
||||
cur = conn.cursor()
|
||||
|
||||
# 1 Core Treasury
|
||||
cur.execute("""
|
||||
INSERT INTO buildings (user_id, b_type, level, x, y, hp, max_hp, status)
|
||||
VALUES (?, 'core', 1, 12, 4, 800, 800, 'active')
|
||||
""", (user_id,))
|
||||
|
||||
# 1 Woodcutter
|
||||
cur.execute("""
|
||||
INSERT INTO buildings (user_id, b_type, level, x, y, hp, max_hp, status)
|
||||
VALUES (?, 'woodcutter', 1, 12, 1, 250, 250, 'active')
|
||||
""", (user_id,))
|
||||
|
||||
# 1 Mine
|
||||
cur.execute("""
|
||||
INSERT INTO buildings (user_id, b_type, level, x, y, hp, max_hp, status)
|
||||
VALUES (?, 'mine', 1, 12, 7, 300, 300, 'active')
|
||||
""", (user_id,))
|
||||
|
||||
# 1 Ballista Tower
|
||||
cur.execute("""
|
||||
INSERT INTO buildings (user_id, b_type, level, x, y, hp, max_hp, status)
|
||||
VALUES (?, 'ballista', 1, 9, 4, 260, 260, 'active')
|
||||
""", (user_id,))
|
||||
|
||||
# Starter defensive walls funneling path
|
||||
starter_walls = [
|
||||
(8, 2), (8, 3), (8, 5), (8, 6),
|
||||
(10, 2), (10, 6)
|
||||
]
|
||||
for wx, wy in starter_walls:
|
||||
cur.execute("""
|
||||
INSERT INTO buildings (user_id, b_type, level, x, y, hp, max_hp, status)
|
||||
VALUES (?, 'wall', 1, ?, ?, 350, 350, 'active')
|
||||
""", (user_id, wx, wy))
|
||||
|
||||
# 1 Hidden Spike Trap in the funnel entrance
|
||||
cur.execute("""
|
||||
INSERT INTO buildings (user_id, b_type, level, x, y, hp, max_hp, status)
|
||||
VALUES (?, 'spike_trap', 1, 8, 4, 50, 50, 'active')
|
||||
""", (user_id,))
|
||||
Reference in New Issue
Block a user