Pārlūkot izejas kodu

Theme boss prompts and allow regeneration

bang 2 nedēļas atpakaļ
vecāks
revīzija
a412903628
2 mainītis faili ar 113 papildinājumiem un 36 dzēšanām
  1. 107 31
      slot_workflow.py
  2. 6 5
      web/index.html

+ 107 - 31
slot_workflow.py

@@ -158,6 +158,109 @@ UI_ART = [
 ]
 
 
+BOSS_BY_THEME = {
+    "jelly": {
+        "title": "果冻糖果关主",
+        "prompt": (
+            "a cute but mischievous giant translucent jelly candy boss, glossy gummy texture, fruit syrup crown, "
+            "candy cane greatsword, jelly armor plates, warm candy-land colors, playful villain expression, "
+            "full body mobile slot game boss asset, no horror, no dark fantasy"
+        ),
+        "material": "glossy translucent jelly candy",
+        "weapon": "candy cane greatsword",
+        "prop": "striped candy coin bag overflowing with gold candy coins",
+    },
+    "fruit": {
+        "title": "水果乐园关主",
+        "prompt": (
+            "a mischievous giant fruit king boss made of glossy juicy fruit pieces, leafy crown, citrus armor, "
+            "banana-shaped sword, bright arcade fruit slot palette, full body mobile game boss asset"
+        ),
+        "material": "glossy juicy fruit armor",
+        "weapon": "banana-shaped golden sword",
+        "prop": "fruit basket coin bag overflowing with gold coins and berries",
+    },
+    "egypt": {
+        "title": "法老宝藏关主",
+        "prompt": (
+            "a cute stylized ancient egypt pharaoh treasure boss, polished gold and turquoise armor, scarab crown, "
+            "ankh-shaped staff sword, sandstone gem palette, full body premium mobile slot boss asset"
+        ),
+        "material": "polished gold turquoise pharaoh armor",
+        "weapon": "ankh-shaped staff sword",
+        "prop": "golden egyptian treasure bag overflowing with coins and scarab gems",
+    },
+    "pirate": {
+        "title": "海盗船长关主",
+        "prompt": (
+            "a playful pirate captain treasure boss, black captain coat with gold trim, oversized pirate hat, "
+            "curved cutlass sword, tropical ocean colors, treasure chest details, full body mobile slot boss asset"
+        ),
+        "material": "pirate captain coat with gold trim",
+        "weapon": "curved pirate cutlass",
+        "prop": "pirate treasure coin bag overflowing with gold doubloons",
+    },
+    "pirate_jelly": {
+        "title": "海盗糖果关主",
+        "prompt": (
+            "a cute translucent jelly pirate captain boss, glossy gummy red candy body, black skull pirate hat, "
+            "wood and gold candy armor, curved candy cutlass, tropical treasure island colors, full body mobile slot boss asset"
+        ),
+        "material": "translucent gummy pirate candy armor",
+        "weapon": "curved candy pirate cutlass",
+        "prop": "purple pirate candy coin bag overflowing with gold coins",
+    },
+    "cyber": {
+        "title": "霓虹机械关主",
+        "prompt": (
+            "a stylized neon cyber arcade boss robot, glowing glass armor panels, electric blue and magenta lights, "
+            "plasma blade, playful mobile game villain silhouette, full body premium slot boss asset"
+        ),
+        "material": "neon glass cyber armor panels",
+        "weapon": "glowing plasma blade",
+        "prop": "holographic coin capsule with neon tokens",
+    },
+}
+
+
+def build_boss_character(slot_config):
+    theme = slot_config["theme"]["key"]
+    profile = BOSS_BY_THEME.get(theme, BOSS_BY_THEME["jelly"])
+    boss_id = slot_config["boss"]["id"]
+    material = profile["material"]
+    weapon = profile["weapon"]
+    prop = profile["prop"]
+    return {
+        "id": boss_id,
+        "type": "spine_parts",
+        "role": "boss",
+        "partGeneration": "sprite_sheet",
+        "spriteSheet": {"enabled": True, "cols": 4, "rows": 4, "size": "1024x1024"},
+        "animations": ["idle", "watch", "charge", "coin_throw", "taunt", "stomp", "attack", "hurt", "explode"],
+        "prompt": (
+            f"{profile['prompt']}, same visual style as {slot_config['theme']['world']}, "
+            "triumphant villain pose, one foot planted forward on a simple defeated hero silhouette, "
+            "no gore, no blood, transparent background"
+        ),
+        "parts": [
+            {"id": "torso", "parent": "root", "x": 0, "y": 180, "prompt": f"torso chest piece of the theme boss, {material}, front view"},
+            {"id": "head", "parent": "torso", "x": 0, "y": 260, "prompt": f"head of the theme boss with expressive villain eyes and matching crown, {material}"},
+            {"id": "left_arm", "parent": "torso", "x": -170, "y": 160, "prompt": f"left arm of the theme boss, {material}, clean separated rigging part"},
+            {"id": "right_arm", "parent": "torso", "x": 170, "y": 170, "prompt": f"right arm of the theme boss gripping weapon handle, {material}"},
+            {"id": "greatsword", "parent": "right_arm", "x": 160, "y": 170, "prompt": f"{weapon}, clean mobile game boss weapon asset"},
+            {"id": "left_leg", "parent": "torso", "x": -85, "y": -80, "prompt": f"left leg and boot of the theme boss, {material}"},
+            {"id": "right_leg", "parent": "torso", "x": 90, "y": -80, "prompt": f"right leg and boot of the theme boss stepping forward, {material}"},
+            {"id": "cape", "parent": "torso", "x": -40, "y": 120, "prompt": f"theme-matching cape or back cloth piece for the boss, {material}"},
+            {"id": "horn_left", "parent": "head", "x": -70, "y": 70, "prompt": "left crown ornament or horn-like silhouette matching the theme boss"},
+            {"id": "horn_right", "parent": "head", "x": 70, "y": 70, "prompt": "right crown ornament or horn-like silhouette matching the theme boss"},
+            {"id": "crack_core", "parent": "torso", "x": 0, "y": 160, "prompt": "glowing magical crack core explosion light matching the boss theme"},
+            {"id": "coin_bag", "parent": "left_arm", "x": -75, "y": -45, "prompt": prop},
+            {"id": "coin_splash", "parent": "torso", "x": 0, "y": 220, "prompt": "arc of shiny gold coins flying outward, transparent background, clean mobile game VFX prop"},
+            {"id": "defeated_hero_shadow", "parent": "root", "x": 95, "y": -185, "prompt": "tiny simple defeated hero silhouette shadow under a boss boot, comedic mobile game style, no gore, no blood"},
+        ],
+    }
+
+
 def slugify(value):
     value = (value or "slot-game").strip().lower()
     value = re.sub(r"[^a-z0-9]+", "-", value).strip("-")
@@ -401,37 +504,10 @@ def build_manifest(slot_config):
         for sid, prompt in symbols
     ]
     if slot_config.get("boss", {}).get("enabled", True):
-        boss_id = slot_config["boss"]["id"]
-        characters.append({
-            "id": boss_id,
-            "type": "spine_parts",
-            "role": "boss",
-            "partGeneration": "sprite_sheet",
-            "spriteSheet": {"enabled": True, "cols": 4, "rows": 4, "size": "1024x1024"},
-            "animations": ["idle", "watch", "charge", "coin_throw", "taunt", "stomp", "attack", "hurt", "explode"],
-            "prompt": (
-                "a cute stylized mobile game demon lord boss character, full body, oversized dark armor, "
-                "horned crown, glowing eyes, holding a huge greatsword, triumphant villain pose, one boot "
-                "planted forward on a simple defeated hero silhouette, no gore, no blood, clean mobile slot "
-                "game boss asset, transparent background"
-            ),
-            "parts": [
-                {"id": "torso", "parent": "root", "x": 0, "y": 180, "prompt": "torso armor chest of a cute demon lord boss, cracked dark armor, front view"},
-                {"id": "head", "parent": "torso", "x": 0, "y": 260, "prompt": "horned demon lord head with glowing eyes and crown, cute villain mobile game style"},
-                {"id": "left_arm", "parent": "torso", "x": -170, "y": 160, "prompt": "left armored arm of demon lord boss with clawed gauntlet"},
-                {"id": "right_arm", "parent": "torso", "x": 170, "y": 170, "prompt": "right armored arm of demon lord boss gripping a sword handle"},
-                {"id": "greatsword", "parent": "right_arm", "x": 160, "y": 170, "prompt": "oversized dark fantasy greatsword, mobile game boss weapon, clean icon asset"},
-                {"id": "left_leg", "parent": "torso", "x": -85, "y": -80, "prompt": "left armored boot leg of demon lord boss, heavy boot"},
-                {"id": "right_leg", "parent": "torso", "x": 90, "y": -80, "prompt": "right armored boot leg of demon lord boss stepping forward"},
-                {"id": "cape", "parent": "torso", "x": -40, "y": 120, "prompt": "torn purple villain cape piece for demon lord boss"},
-                {"id": "horn_left", "parent": "head", "x": -70, "y": 70, "prompt": "left curved horn of demon lord boss"},
-                {"id": "horn_right", "parent": "head", "x": 70, "y": 70, "prompt": "right curved horn of demon lord boss"},
-                {"id": "crack_core", "parent": "torso", "x": 0, "y": 160, "prompt": "glowing purple gold magical crack core explosion light for boss armor"},
-                {"id": "coin_bag", "parent": "left_arm", "x": -75, "y": -45, "prompt": "small purple villain coin bag overflowing with shiny gold coins, mobile game boss prop"},
-                {"id": "coin_splash", "parent": "torso", "x": 0, "y": 220, "prompt": "arc of shiny gold coins flying outward, transparent background, clean mobile game VFX prop"},
-                {"id": "defeated_hero_shadow", "parent": "root", "x": 95, "y": -185, "prompt": "tiny simple defeated hero silhouette shadow under a demon boss boot, comedic mobile game style, no gore, no blood"},
-            ],
-        })
+        profile = BOSS_BY_THEME.get(theme, BOSS_BY_THEME["jelly"])
+        if slot_config["boss"].get("title") == "大魔王关主":
+            slot_config["boss"]["title"] = profile["title"]
+        characters.append(build_boss_character(slot_config))
 
     return {
         "game": slot_config["game"]["id"],

+ 6 - 5
web/index.html

@@ -382,7 +382,7 @@ function renderChars(v){
         动作: ${esc((done?anims:t.animations||[]).join(', ')||'idle')}</div>
       ${done&&isParts?'<button class="ghost parts-btn">查看拆件图</button>':''}
       <div class="task-prompt">${esc(t.prompt||'')}</div>
-      ${done?'':`<button class="ghost retry-btn" data-kind="characters" data-id="${esc(t.id)}" ${running?'disabled':''}>${running?'正在生成…':'补生成 / 重试'}</button>`}`;
+      <button class="ghost retry-btn" data-kind="characters" data-id="${esc(t.id)}" ${running?'disabled':''}>${running?'正在生成…':(done?'重新生成':'补生成 / 重试')}</button>`;
     if(done&&anims.length){
       const img=card.querySelector('img'), selA=card.querySelector('select');
       const tgt={el:img, anim:animMap[anims[0]], start:performance.now()};
@@ -414,7 +414,7 @@ function renderArt(v){
       <div class="meta">${done?`${a.w}×${a.h}px · ${a.transparent?'透明素材':'整图背景'}`:`${esc(t.size||'')} · ${t.transparent?'透明素材':'整图背景'}`}<br>
         <span class="pill">ui_art</span> ${esc(t.use)}</div>
       <div class="task-prompt">${esc(t.prompt||'')}</div>
-      ${done?'':`<button class="ghost retry-btn" data-kind="ui_art" data-id="${esc(t.id)}" ${running?'disabled':''}>${running?'正在生成…':'补生成 / 重试'}</button>`}`;
+      <button class="ghost retry-btn" data-kind="ui_art" data-id="${esc(t.id)}" ${running?'disabled':''}>${running?'正在生成…':(done?'重新生成':'补生成 / 重试')}</button>`;
     grid.appendChild(card);
   });
   v.appendChild(grid);
@@ -435,7 +435,7 @@ function renderVfx(v){
         <span class="task-status ${done?'done':(running?'running':'missing')}">${done?'已生成':(running?'生成中':'缺失')}</span></div>
       <div class="meta"><span class="pill">particle</span> ${done?`模板: ${esc(x.template)} · 发射率 ${x.config.emissionRate}/s · 寿命 ${x.config.life}s`:esc(t.use)}</div>
       <div class="task-prompt">${esc(t.prompt||'')}</div>
-      ${done?'':`<button class="ghost retry-btn" data-kind="vfx" data-id="${esc(t.id)}" ${running?'disabled':''}>${running?'正在生成…':'补生成 / 重试'}</button>`}`;
+      <button class="ghost retry-btn" data-kind="vfx" data-id="${esc(t.id)}" ${running?'disabled':''}>${running?'正在生成…':(done?'重新生成':'补生成 / 重试')}</button>`;
     grid.appendChild(card);
     if(done) startParticle(card.querySelector('canvas'), x.config);
   });
@@ -520,10 +520,11 @@ function renderUi(v){
       <div class="task-head"><div><div class="name">${esc(t.chineseName)}</div><div class="meta">${esc(t.englishName)}</div></div>
         <span class="task-status ${done?'done':(running?'running':'missing')}">${done?'已生成':(running?'生成中':'缺失')}</span></div>
       <div class="meta"><span class="pill">tween</span> 预设: ${esc(preset)}<br>${esc(t.use)}</div>
-      ${done?'<button class="ghost">▶ 播放</button>':`<button class="ghost retry-btn" data-kind="ui" data-id="${esc(t.id)}" ${running?'disabled':''}>${running?'正在生成…':'补生成 / 重试'}</button>`}`;
+      ${done?'<button class="ghost play-btn">▶ 播放</button>':''}
+      <button class="ghost retry-btn" data-kind="ui" data-id="${esc(t.id)}" ${running?'disabled':''}>${running?'正在生成…':(done?'重新生成':'补生成 / 重试')}</button>`;
     if(!done){ grid.appendChild(card); return; }
     const box=card.querySelector('.demo-box');
-    card.querySelector('button').onclick=()=>{
+    card.querySelector('.play-btn').onclick=()=>{
       box.style.opacity=1;box.style.transform='';
       (TWEEN_DEMO[u.preset]||(()=>{}))(box);
     };