怪物X联盟 · 单机版
by TurboBolt40637 lines26.9 KB
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>怪物X联盟 · 单机版</title>
<style>
* { margin:0; padding:0; box-sizing:border-box; }
body {
font-family:"Microsoft YaHei","PingFang SC",sans-serif;
background:linear-gradient(135deg,#1a1a2e 0%,#16213e 50%,#0f3460 100%);
color:#eee; min-height:100vh; padding:12px;
}
#app { max-width:880px; margin:0 auto; }
h1 {
text-align:center; font-size:26px; margin-bottom:4px;
background:linear-gradient(90deg,#e94560,#f5a623);
-webkit-background-clip:text; -webkit-text-fill-color:transparent;
text-shadow:none; letter-spacing:2px;
}
.subtitle { text-align:center; color:#888; font-size:12px; margin-bottom:14px; }
.panel {
background:rgba(255,255,255,0.05); border-radius:12px; padding:14px;
margin-bottom:12px; border:1px solid rgba(255,255,255,0.08);
backdrop-filter:blur(4px);
}
.battlefield { display:flex; justify-content:space-between; align-items:flex-end; gap:12px; }
.combatant { flex:1; }
.combatant.foe { text-align:right; }
.monster-art {
font-size:64px; line-height:1; margin:6px 0;
filter:drop-shadow(0 4px 6px rgba(0,0,0,.5));
}
.name-row { display:flex; justify-content:space-between; align-items:center; font-weight:bold; }
.foe .name-row { flex-direction:row-reverse; }
.lv { font-size:12px; color:#f5a623; }
.hp-bar {
height:14px; background:#333; border-radius:7px; overflow:hidden; margin:4px 0;
border:1px solid rgba(255,255,255,.15);
}
.hp-fill {
height:100%; background:linear-gradient(90deg,#2ecc71,#27ae60);
transition:width .4s ease; border-radius:7px;
}
.hp-fill.mid { background:linear-gradient(90deg,#f5a623,#e67e22); }
.hp-fill.low { background:linear-gradient(90deg,#e74c3c,#c0392b); }
.hp-text { font-size:11px; color:#aaa; }
.type-badge {
display:inline-block; padding:2px 8px; border-radius:10px; font-size:11px;
margin-left:6px; color:#fff; font-weight:normal;
}
.vs { font-size:24px; color:#e94560; align-self:center; font-weight:bold; }
.skills { display:grid; grid-template-columns:1fr 1fr; gap:8px; margin-top:6px; }
button {
font-family:inherit; cursor:pointer; border:none; border-radius:8px;
padding:10px 12px; font-size:14px; color:#fff; transition:.15s;
background:rgba(255,255,255,0.1); border:1px solid rgba(255,255,255,0.12);
}
button:hover:not(:disabled) { transform:translateY(-1px); filter:brightness(1.2); }
button:active:not(:disabled) { transform:translateY(0); }
button:disabled { opacity:.4; cursor:not-allowed; }
.skill-btn { text-align:left; }
.skill-btn .pp { float:right; color:#aaa; font-size:11px; }
.skill-btn .cat {
font-size:10px; padding:1px 6px; border-radius:8px; margin-right:6px;
}
.cat.phys { background:#c0392b; }
.cat.magic { background:#2980b9; }
.cat.status { background:#27ae60; }
.action-row { display:grid; grid-template-columns:repeat(4,1fr); gap:8px; }
.btn-catch { background:linear-gradient(90deg,#e94560,#c62a74); }
.btn-bag { background:linear-gradient(90deg,#16a085,#138d75); }
.btn-run { background:linear-gradient(90deg,#7f8c8d,#636e72); }
.btn-next { background:linear-gradient(90deg,#2980b9,#1f618d); }
.log {
height:120px; overflow-y:auto; font-size:13px; line-height:1.5;
background:rgba(0,0,0,0.3); border-radius:8px; padding:8px 10px;
border:1px solid rgba(255,255,255,0.06);
}
.log p { margin:2px 0; }
.log .dmg { color:#e74c3c; }
.log .heal { color:#2ecc71; }
.log .info { color:#3498db; }
.log .sys { color:#f5a623; }
.map-bar { display:flex; flex-wrap:wrap; gap:6px; align-items:center; }
.map-node {
padding:4px 10px; border-radius:12px; font-size:12px;
background:rgba(255,255,255,0.07); border:1px solid transparent;
}
.map-node.active { border-color:#f5a623; color:#f5a623; }
.map-node.cleared { color:#2ecc71; }
.map-node.boss { color:#e94560; font-weight:bold; }
.team-strip { display:flex; gap:8px; flex-wrap:wrap; }
.team-member {
padding:6px 10px; border-radius:8px; font-size:12px;
background:rgba(255,255,255,0.06); border:1px solid rgba(255,255,255,0.1);
cursor:pointer; transition:.15s;
}
.team-member:hover { border-color:#f5a623; }
.team-member.active { border-color:#2ecc71; background:rgba(46,204,113,0.15); }
.team-member.fainted { opacity:.4; }
.modal-mask {
position:fixed; inset:0; background:rgba(0,0,0,0.7);
display:none; align-items:center; justify-content:center; z-index:50;
padding:20px;
}
.modal-mask.show { display:flex; }
.modal {
background:#1a1a2e; border-radius:16px; padding:24px;
max-width:520px; width:100%; max-height:85vh; overflow-y:auto;
border:1px solid rgba(255,255,255,0.12);
}
.modal h2 { color:#f5a623; margin-bottom:12px; text-align:center; }
.dex-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(140px,1fr)); gap:8px; }
.dex-entry {
background:rgba(255,255,255,0.05); border-radius:8px; padding:8px;
font-size:12px; border:1px solid rgba(255,255,255,0.08);
}
.dex-entry.locked { opacity:.35; }
.stat-line { display:flex; justify-content:space-between; font-size:11px; color:#bbb; }
.close-btn { margin-top:16px; width:100%; background:linear-gradient(90deg,#e94560,#c62a74); }
.hint { font-size:11px; color:#666; text-align:center; margin-top:8px; }
.topbar { display:flex; justify-content:space-between; align-items:center; margin-bottom:10px; }
.coins { color:#f5a623; font-weight:bold; }
.toolbar { display:flex; gap:6px; }
.toolbar button { padding:6px 10px; font-size:12px; }
@media(max-width:560px){
.skills,.action-row { grid-template-columns:1fr 1fr; }
.monster-art { font-size:48px; }
h1 { font-size:20px; }
}
</style>
</head>
<body>
<div id="app">
<h1>⚔️ 怪物X联盟 · 单机版</h1>
<div class="subtitle">魁星岛 · 训练师冒险 — 收集 · 养成 · 对战</div>
<div class="panel">
<div class="topbar">
<div>🗺️ <span id="stageName">孢子林</span> · 第 <span id="stageIdx">1</span>/<span id="stageTotal">10</span> 关</div>
<div class="coins">💰 G币: <span id="coins">100</span> | 🧪 精灵球: <span id="balls">5</span></div>
</div>
<div class="map-bar" id="mapBar"></div>
</div>
<div class="panel">
<div class="battlefield">
<div class="combatant foe">
<div class="monster-art" id="foeArt">👾</div>
<div class="name-row">
<span id="foeName">野生怪物</span>
<span class="lv">Lv.<span id="foeLv">1</span></span>
</div>
<span class="type-badge" id="foeType" style="background:#666">普通</span>
<div class="hp-bar"><div class="hp-fill" id="foeHpBar"></div></div>
<div class="hp-text"><span id="foeHp">30</span>/<span id="foeMaxHp">30</span></div>
</div>
<div class="vs">VS</div>
<div class="combatant">
<div class="monster-art" id="myArt">🐣</div>
<div class="name-row">
<span id="myName">??</span>
<span class="lv">Lv.<span id="myLv">1</span></span>
</div>
<span class="type-badge" id="myType" style="background:#666">普通</span>
<div class="hp-bar"><div class="hp-fill" id="myHpBar"></div></div>
<div class="hp-text"><span id="myHp">30</span>/<span id="myMaxHp">30</span> · EXP <span id="myExp">0</span>/<span id="myNextExp">20</span></div>
</div>
</div>
<div class="skills" id="skillPanel"></div>
<div class="action-row" style="margin-top:10px">
<button class="btn-catch" onclick="doCatch()">🧪 捕捉 (<span id="ballCount">5</span>)</button>
<button class="btn-bag" onclick="usePotion()">🧪 药水 (<span id="potions">2</span>)</button>
<button class="btn-run" onclick="runAway()">🏃 逃跑</button>
<button class="btn-next" onclick="nextStage()" id="nextBtn" style="display:none">➡️ 下一关</button>
</div>
</div>
<div class="panel">
<div style="font-size:12px;color:#888;margin-bottom:4px">⚔️ 战斗日志</div>
<div class="log" id="log"></div>
</div>
<div class="panel">
<div style="font-size:12px;color:#888;margin-bottom:6px">🎒 我的队伍 (点击切换出战)</div>
<div class="team-strip" id="teamStrip"></div>
<div class="toolbar" style="margin-top:10px">
<button onclick="openDex()">📖 图鉴</button>
<button onclick="openHelp()">❓ 玩法</button>
<button onclick="resetGame()">🔄 重置存档</button>
</div>
<div class="hint">提示:削弱敌方血量后再捕捉成功率更高 · 属性相克 ×1.2 伤害 · 进度自动保存</div>
</div>
</div>
<!-- 图鉴 / 帮助 弹窗 -->
<div class="modal-mask" id="modalMask">
<div class="modal" id="modalContent"></div>
</div>
<script>
/* ============ 数据层:系别 / 相克 / 技能 / 宠物 ============ */
const TYPES = {
火:{color:'#e74c3c', art:'🔥'}, 水:{color:'#3498db', art:'💧'},
草:{color:'#2ecc71', art:'🌿'}, 电:{color:'#f5d020', art:'⚡'},
地:{color:'#b9770e', art:'⛰️'}, 飞行:{color:'#a8e6cf', art:'🕊️'},
格斗:{color:'#e67e22', art:'🥊'}, 超能:{color:'#9b59b6', art:'🔮'},
鬼:{color:'#34495e', art:'👻'}, 龙:{color:'#e84393', art:'🐉'},
普通:{color:'#95a5a6', art:'⭐'}
};
// 相克表:atk -> 被克制的防守系(伤害×1.2)
const WEAK = {
火:['草','飞行'], 水:['火','地'], 草:['水','地'],
电:['水','飞行'], 地:['电','火','格斗'], 飞行:['草','虫'],
格斗:['普通','超能'], 超能:['格斗','鬼'], 鬼:['超能','鬼'],
龙:['龙'], 普通:[]
};
function typeMultiplier(atkType, defType){
if(WEAK[atkType] && WEAK[atkType].includes(defType)) return 1.2;
// 反向:若防守系克制攻击系,则伤害减半
if(WEAK[defType] && WEAK[defType].includes(atkType)) return 0.6;
return 1.0;
}
// 技能池
const SKILLS = {
撞击:{cat:'phys', power:18, pp:25, type:'普通', desc:'基础物理攻击'},
火花:{cat:'magic', power:22, pp:20, type:'火', desc:'喷出小火苗'},
喷射火焰:{cat:'magic', power:40, pp:10, type:'火', desc:'强力火焰攻击'},
水枪:{cat:'magic', power:22, pp:20, type:'水', desc:'高压水流'},
冲浪:{cat:'magic', power:40, pp:10, type:'水', desc:'巨浪拍击'},
飞叶快刀:{cat:'phys', power:22, pp:20, type:'草', desc:'锋利叶片切割'},
阳光烈焰:{cat:'magic', power:45, pp:8, type:'草', desc:'汇聚日光爆发'},
电击:{cat:'magic', power:22, pp:20, type:'电', desc:'释放电流'},
十万伏特:{cat:'magic', power:42, pp:10, type:'电', desc:'狂暴雷电'},
震级:{cat:'phys', power:24, pp:18, type:'地', desc:'引发地震'},
大地之力:{cat:'magic', power:42, pp:10, type:'地', desc:'地脉能量爆发'},
啄击:{cat:'phys', power:20, pp:22, type:'飞行', desc:'用尖喙啄击'},
空气斩:{cat:'phys', power:38, pp:12, type:'飞行', desc:'空气刃攻击'},
爆裂拳:{cat:'phys', power:24, pp:18, type:'格斗', desc:'猛烈拳击'},
气合拳:{cat:'phys', power:42, pp:10, type:'格斗', desc:'蓄力重拳'},
念力:{cat:'magic', power:22, pp:20, type:'超能', desc:'精神冲击'},
精神强念:{cat:'magic', power:42, pp:10, type:'超能', desc:'强烈精神波'},
暗影拳:{cat:'phys', power:22, pp:20, type:'鬼', desc:'幽冥拳击'},
暗影球:{cat:'magic', power:40, pp:10, type:'鬼', desc:'怨念能量球'},
龙息:{cat:'magic', power:24, pp:18, type:'龙', desc:'龙之吐息'},
龙星群:{cat:'magic', power:45, pp:8, type:'龙', desc:'降下流星雨'},
空手切:{cat:'phys', power:20, pp:22, type:'普通', desc:'手刀劈砍'},
居合斩:{cat:'phys', power:38, pp:12, type:'普通', desc:'快速斩击'},
铁壁:{cat:'status', power:0, pp:15, type:'普通', desc:'自身物防+5', buff:{def:5}},
冥想:{cat:'status', power:0, pp:15, type:'超能', desc:'自身魔攻+5', buff:{matk:5}},
治愈:{cat:'status', power:0, pp:8, type:'普通', desc:'恢复30%HP', heal:0.3}
};
// 宠物模板(基于怪物X联盟资料抽象设计,D~S 等阶)
const SPECIES = {
// D阶
喇叭芽:{tier:'D', type:'草', base:{hp:30,atk:35,def:26,spd:25,matk:30,mdef:17},
art:'🌱', skills:['连环踢','飞叶快刀'], evolveTo:null, evolveLv:0,
habitat:'孢子林', dex:'叶片如鞭,初入魁星岛的训练师伙伴。'},
小海螺:{tier:'D', type:'普通', base:{hp:32,atk:26,def:28,spd:22,matk:35,mdef:18},
art:'🐚', skills:['喷水','硬邦邦'], evolveTo:null, evolveLv:0,
habitat:'陨星湖', dex:'壳硬如铁,湖畔常见的小生物。'},
小蝙蝠:{tier:'D', type:'普通', base:{hp:32,atk:27,def:21,spd:39,matk:32,mdef:27},
art:'🦇', skills:['吹风','吼叫'], evolveTo:null, evolveLv:0,
habitat:'晶石山', dex:'振翅于洞穴,速度敏捷。'},
// C阶
黑鱼:{tier:'C', type:'普通', base:{hp:36,atk:30,def:32,spd:25,matk:46,mdef:36},
art:'🐟', skills:['喷水'], evolveTo:null, evolveLv:0,
habitat:'陨星湖', dex:'深水中的灵鱼,魔力充沛。'},
小花蜗:{tier:'C', type:'普通', base:{hp:40,atk:44,def:35,spd:32,matk:37,mdef:28},
art:'🐌', skills:['飞叶快刀','弹性皮肤'], evolveTo:'食人花', evolveLv:16,
habitat:'神秘林', dex:'背壳开花,可进化为食人花。'},
仙人球:{tier:'C', type:'普通', base:{hp:35,atk:40,def:38,spd:32,matk:32,mdef:30},
art:'🌵', skills:['空手切','反击'], evolveTo:'仙人掌', evolveLv:18,
habitat:'巨兽矿野', dex:'沙漠刺球,格斗天赋初显。'},
// B阶
不死鸟:{tier:'B', type:'火', base:{hp:38,atk:37,def:37,spd:39,matk:55,mdef:51},
art:'🐦🔥', skills:['灵光一闪','沙幕'], evolveTo:null, evolveLv:0,
habitat:'赫里奥斯', dex:'浴火之鸟,火焰的使者。'},
食人花:{tier:'B', type:'普通', base:{hp:49,atk:66,def:30,spd:34,matk:43,mdef:42},
art:'🌺', skills:['吸取','飞叶快刀','究极吸取'], evolveTo:null, evolveLv:0,
habitat:'远征哨岗', dex:'由小花蜗进化,吞噬光线的猎手。'},
魅灵:{tier:'B', type:'鬼', base:{hp:41,atk:34,def:41,spd:51,matk:62,mdef:38},
art:'👻', skills:['吸取','寄生种子','暗影波'], evolveTo:null, evolveLv:0,
habitat:'弗瑞斯', dex:'飘荡的灵体,擅长魔法。'},
// A阶
劲帕奇:{tier:'A', type:'飞行', base:{hp:41,atk:66,def:39,spd:58,matk:63,mdef:50},
art:'🦅', skills:['电击','电火花','雷之牙','击穿'], evolveTo:null, evolveLv:0,
habitat:'天域', dex:'翱翔天域的电翼猛禽。'},
仙人掌:{tier:'A', type:'格斗', base:{hp:42,atk:75,def:58,spd:40,matk:43,mdef:58},
art:'🥷', skills:['空手切','反击','十字拳','格斗领域'], evolveTo:null, evolveLv:0,
habitat:'远古墓地', dex:'仙人球的最终形态,格斗大师。'},
凤凰花:{tier:'A', type:'火', base:{hp:45,atk:52,def:45,spd:39,matk:72,mdef:60},
art:'🌻', skills:['藤鞭','摇摆','究极吸取','火焰之舞','浴火重生'], evolveTo:null, evolveLv:0,
habitat:'太阳井', dex:'太阳井畔的不朽之花。'},
// S阶
双角巨龙:{tier:'S', type:'龙', base:{hp:56,atk:60,def:58,spd:51,matk:71,mdef:60},
art:'🐲', skills:['石锤','挖洞','碎石','地之盟约','大地之力'], evolveTo:null, evolveLv:0,
habitat:'神秘林', dex:'神秘林的远古龙裔,传说级存在。'},
霹雳弹:{tier:'S', type:'火', base:{hp:30,atk:10,def:42,spd:22,matk:200,mdef:42},
art:'💣', skills:['高压电击','十万伏特','百万伏特'], evolveTo:null, evolveLv:0,
habitat:'虚无之地', dex:'魔力极致的禁忌造物。'},
泰坦:{tier:'S', type:'普通', base:{hp:70,atk:69,def:63,spd:36,matk:53,mdef:45},
art:'🗿', skills:['精神波动','能量护盾','思念头槌','刀枪不入'], evolveTo:null, evolveLv:0,
habitat:'天堂城', dex:'天堂城的守护巨神。'}
};
// 关卡地图(魁星岛冒险路线)
const STAGES = [
{name:'孢子林', wilds:['喇叭芽','小海螺','小蝙蝠'], lvRange:[1,3], boss:null},
{name:'陨星湖', wilds:['小海螺','黑鱼','小蝙蝠'], lvRange:[3,5], boss:null},
{name:'晶石山', wilds:['小蝙蝠','仙人球','黑鱼'], lvRange:[5,7], boss:null},
{name:'神秘林', wilds:['小花蜗','小海螺','仙人球'], lvRange:[7,9], boss:{species:'双角巨龙', lv:10}},
{name:'巨兽矿野', wilds:['仙人球','小花蜗'], lvRange:[9,11], boss:null},
{name:'弗瑞斯', wilds:['魅力灵','小花蜗'], lvRange:[11,13], boss:null},
{name:'赫里奥斯', wilds:['不死鸟','仙人球'], lvRange:[13,15], boss:null},
{name:'太阳井', wilds:['凤凰花','不死鸟'], lvRange:[15,17], boss:{species:'凤凰花', lv:18}},
{name:'天域', wilds:['劲帕奇','不死鸟'], lvRange:[17,19], boss:null},
{name:'龙之首脊', wilds:['劲帕奇','魅力灵'], lvRange:[19,21], boss:{species:'双角巨龙', lv:22}}
];
/* ============ 游戏状态 ============ */
let G = null;
function defaultState(){
// 初始队伍:喇叭芽 Lv1
const starter = makeMon('喇叭芽', 1);
return {
stage:0, coins:100, balls:5, potions:2,
team:[starter], active:0,
cleared:new Array(STAGES.length).fill(false),
dex:{}, // name -> true 已捕获
dexSeen:{}
};
}
function makeMon(speciesName, lv){
const sp = SPECIES[speciesName];
const b = sp.base;
// 成长曲线:属性 = base * (1 + lv*0.12)
const mult = 1 + (lv-1)*0.12;
const mon = {
species:speciesName, lv,
type:sp.type, tier:sp.tier, art:sp.art,
maxHp: Math.floor(b.hp * mult),
hp: Math.floor(b.hp * mult),
atk: Math.floor(b.atk * mult),
def: Math.floor(b.def * mult),
matk: Math.floor(b.matk * mult),
mdef: Math.floor(b.mdef * mult),
spd: Math.floor(b.spd * mult),
exp:0, nextExp: 20 + lv*10,
skills: sp.skills.map(n => ({name:n, ...SKILLS[n], maxpp:SKILLS[n].pp, pp:SKILLS[n].pp})),
buffs:{def:0, matk:0}
};
return mon;
}
function rand(a,b){ return Math.floor(Math.random()*(b-a+1))+a; }
function clamp(v,a,b){ return Math.max(a,Math.min(b,v)); }
/* ============ 战斗逻辑 ============ */
let foe = null;
let inBattle = false;
let busy = false;
function calcDamage(attacker, defender, skill){
const atkStat = skill.cat==='phys' ? attacker.atk + attacker.buffs.def*0 : attacker.matk + attacker.buffs.matk*0;
const defStat = skill.cat==='phys' ? defender.def + defender.buffs.def : defender.mdef + defender.buffs.matk;
const baseRatio = skill.cat==='phys'
? (attacker.atk / Math.max(1, defender.def))
: (attacker.matk / Math.max(1, defender.mdef));
let dmg = skill.power * baseRatio * 0.8;
dmg *= typeMultiplier(skill.type, defender.type);
dmg *= 0.85 + Math.random()*0.3; // 浮动
return Math.max(1, Math.floor(dmg));
}
function logMsg(msg, cls=''){
const el = document.getElementById('log');
const p = document.createElement('p');
p.className = cls;
p.textContent = msg;
el.appendChild(p);
el.scrollTop = el.scrollHeight;
}
function renderAll(){
renderMap();
renderTeam();
renderActive();
renderFoe();
renderSkills();
document.getElementById('coins').textContent = G.coins;
document.getElementById('balls').textContent = G.balls;
document.getElementById('ballCount').textContent = G.balls;
document.getElementById('potions').textContent = G.potions;
}
function renderMap(){
document.getElementById('stageName').textContent = STAGES[G.stage].name;
document.getElementById('stageIdx').textContent = G.stage+1;
document.getElementById('stageTotal').textContent = STAGES.length;
const bar = document.getElementById('mapBar');
bar.innerHTML = '';
STAGES.forEach((s,i)=>{
const n = document.createElement('span');
n.className = 'map-node' + (i===G.stage?' active':'') + (G.cleared[i]?' cleared':'') + (s.boss?' boss':'');
n.textContent = (s.boss?'👑 ':'') + s.name;
bar.appendChild(n);
});
}
function renderTeam(){
const strip = document.getElementById('teamStrip');
strip.innerHTML = '';
G.team.forEach((m,i)=>{
const d = document.createElement('div');
d.className = 'team-member' + (i===G.active?' active':'') + (m.hp<=0?' fainted':'');
d.innerHTML = `${m.art} ${m.species}<br>Lv.${m.lv} ${m.hp>0?m.hp+'/'+m.maxHp:'倒下'}`;
d.onclick = ()=>{ if(inBattle) return; if(m.hp>0){ G.active=i; save(); renderAll(); } };
strip.appendChild(d);
});
}
function renderActive(){
const m = G.team[G.active];
document.getElementById('myArt').textContent = m.art;
document.getElementById('myName').textContent = m.species;
document.getElementById('myLv').textContent = m.lv;
document.getElementById('myType').textContent = m.type;
document.getElementById('myType').style.background = TYPES[m.type].color;
const ratio = m.hp/m.maxHp;
const bar = document.getElementById('myHpBar');
bar.style.width = (ratio*100)+'%';
bar.className = 'hp-fill' + (ratio<0.3?' low':ratio<0.6?' mid':'');
document.getElementById('myHp').textContent = m.hp;
document.getElementById('myMaxHp').textContent = m.maxHp;
document.getElementById('myExp').textContent = m.exp;
document.getElementById('myNextExp').textContent = m.nextExp;
}
function renderFoe(){
if(!foe){ return; }
document.getElementById('foeArt').textContent = foe.art;
document.getElementById('foeName').textContent = foe.isWild ? '野生 '+foe.species : foe.species;
document.getElementById('foeLv').textContent = foe.lv;
document.getElementById('foeType').textContent = foe.type;
document.getElementById('foeType').style.background = TYPES[foe.type].color;
const ratio = foe.hp/foe.maxHp;
const bar = document.getElementById('foeHpBar');
bar.style.width = (ratio*100)+'%';
bar.className = 'hp-fill' + (ratio<0.3?' low':ratio<0.6?' mid':'');
document.getElementById('foeHp').textContent = foe.hp;
document.getElementById('foeMaxHp').textContent = foe.maxHp;
}
function renderSkills(){
const m = G.team[G.active];
const panel = document.getElementById('skillPanel');
panel.innerHTML = '';
m.skills.forEach((s,i)=>{
const btn = document.createElement('button');
btn.className = 'skill-btn';
btn.disabled = busy || s.pp<=0 || !inBattle;
btn.innerHTML = `<span class="cat ${s.cat}">${s.cat==='phys'?'物':s.cat==='magic'?'魔':'辅'}</span>${s.name} <span class="pp">PP ${s.pp}/${s.maxpp}</span>`;
btn.title = s.desc;
btn.onclick = ()=> useSkill(i);
panel.appendChild(btn);
});
}
/* ---- 遭遇战 ---- */
function startEncounter(){
const stage = STAGES[G.stage];
// 检查是否触发 BOSS
if(stage.boss && !G.cleared[G.stage]){
foe = makeMon(stage.boss.species, stage.boss.lv);
foe.isWild = false;
} else {
const pool = stage.wilds;
const spName = pool[rand(0,pool.length-1)];
const lv = rand(stage.lvRange[0], stage.lvRange[1]);
foe = makeMon(spName, lv);
foe.isWild = true;
}
foe.hp = foe.maxHp;
inBattle = true; busy = false;
document.getElementById('nextBtn').style.display = 'none';
// 记录图鉴
G.dexSeen[foe.species] = true;
save();
logMsg(`⚔️ 遭遇 ${foe.isWild?'野生':''}${foe.species} Lv.${foe.lv}!`, 'sys');
renderAll();
}
/* ---- 使用技能 ---- */
function useSkill(idx){
if(busy || !inBattle) return;
const me = G.team[G.active];
const sk = me.skills[idx];
if(sk.pp<=0){ logMsg('PP不足!', 'info'); return; }
busy = true;
renderSkills();
sk.pp--;
// 玩家行动
if(sk.cat==='status'){
if(sk.buff){
if(sk.buff.def) { me.buffs.def += sk.buff.def; logMsg(`${me.species} 使用 ${sk.name},物防提升!`, 'heal'); }
if(sk.buff.matk){ me.buffs.matk += sk.buff.matk; logMsg(`${me.species} 使用 ${sk.name},魔攻提升!`, 'heal'); }
}
if(sk.heal){
const heal = Math.floor(me.maxHp * sk.heal);
me.hp = clamp(me.hp+heal, 0, me.maxHp);
logMsg(`${me.species} 使用 ${sk.name},恢复 ${heal} HP!`, 'heal');
}
} else {
const dmg = calcDamage(me, foe, sk);
const mult = typeMultiplier(sk.type, foe.type);
foe.hp = clamp(foe.hp-dmg, 0, foe.maxHp);
let eff = mult>1 ? ' 效果拔群!' : mult<1 ? ' 效果不佳…' : '';
logMsg(`${me.species} 使用 ${sk.name},造成 ${dmg} 伤害${eff}`, 'dmg');
}
renderActive(); renderFoe();
if(foe.hp<=0){ winBattle(); return; }
// 敌方行动(速度判定)
setTimeout(()=>{
if(me.spd >= foe.spd){
enemyTurn();
} else {
logMsg(`${foe.species} 速度更快,先发制人!`, 'info');
enemyTurn();
}
}, 500);
}
function enemyTurn(){
if(busy===false) {} // noop
const me = G.team[G.active];
// 敌方选技能:优先有PP的攻击技
const atkSkills = foe.skills.filter(s=>s.cat!=='status' && s.pp>0);
if(atkSkills.length===0){
logMsg(`${foe.species} 无技可施!`, 'info');
finishEnemyTurn();
return;
}
const sk = atkSkills[rand(0,atkSkills.length-1)];
sk.pp--;
const dmg = calcDamage(foe, me, sk);
const mult = typeMultiplier(sk.type, me.type);
me.hp = clamp(me.hp-dmg, 0, me.maxHp);
let eff = mult>1 ? ' 效果拔群!' : mult<1 ? ' 效果不佳…' : '';
logMsg(`${foe.species} 使用 ${sk.name},对你造成 ${dmg} 伤害${eff}`, 'dmg');
renderActive();
if(me.hp<=0){
logMsg(`💀 ${me.species} 倒下了!`, 'sys');
// 切换到下一只
const aliveIdx = G.team.findIndex(m=>m.hp>0);
if(aliveIdx>=0){
G.active = aliveIdx;
logMsg(`👉 派出 ${G.team[aliveIdx].species}!`, 'info');
renderAll();
busy=false; renderSkills();
} else {
logMsg(`🚨 全队覆灭!逃回营地…`, 'sys');
// 复活全队,回到当前关卡起点
G.team.forEach(m=>{ m.hp = m.maxHp; });
G.coins = Math.max(0, G.coins-20);
inBattle=false; foe=null;
renderAll();
}
return;
}
finishEnemyTurn();
}
function finishEnemyTurn(){
busy=false;
renderSkills();
renderFoe();
}
/* ---- 胜利结算 ---- */
function winBattle(){
inBattle = false;
const reward = foe.isWild ? rand(8,15) : 50;
G.coins += reward;
logMsg(`🎉 击败了 ${foe.species}!获得 ${reward} G币`, 'sys');
// 分配经验给全队
const expGain = foe.isWild ? rand(10,18) : 60;
G.team.forEach(m=>{
if(m.hp>0){
m.exp += expGain;
logMsg(`${m.species} 获得 ${expGain} 经验`, 'info');
while(m.exp >= m.nextExp){
m.exp -= m.nextExp;
m.lv++;
m.maxHp += Math.floor(SPECIES[m.species].base.hp*0.12);
m.hp = m.maxHp;
m.atk += Math.floor(SPECIES[m.species].base.atk*0.12);
m.def += Math.floor(SPECIES[m.species].base.def*0.12);
m.matk += Math.floor(SPECIES[m.species].base.matk*0.12);
m.mdef += Math.floor(SPECIES[m.species].base.mdef*0.12);
m.spd += Math.floor(SPECIES[m.species].base.spd*0.12);
m.nextExp = 20 + m.lv*10;
logMsg(`✨ ${m.species} 升到 Lv.${m.lv}!`, 'heal');
// 进化判定
const sp = SPECIES[m.species];
if(sp.evolveTo && m.lv >= sp.evolveLv){
const oldName = m.species;
const evolved = makeMon(sp.evolveTo, m.lv);
evolved.exp = m.exp;
const idx = G.team.indexOf(m);
G.team[idx] = evolved;
if(G.active===idx) G.active=idx;
logMsg(`🌟 ${oldName} 进化为 ${sp.evolveTo}!`, 'sys');
}
}
}
});
// 标记关卡清除
Game Source: 怪物X联盟 · 单机版
Creator: TurboBolt40
Libraries: none
Complexity: complex (637 lines, 26.9 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: x-turbobolt40" to link back to the original. Then publish at arcadelab.ai/publish.