922 lines
34 KiB
HTML
922 lines
34 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<title>多词条 Affix Editor(支持导入、编辑多个)</title>
|
||
<style>
|
||
body {
|
||
font-family: sans-serif;
|
||
margin: 20px;
|
||
background: #f9f9f9;
|
||
}
|
||
h1 {
|
||
margin-bottom: 10px;
|
||
}
|
||
fieldset {
|
||
margin: 10px 0;
|
||
border: 1px solid #ccc;
|
||
padding: 10px;
|
||
}
|
||
legend {
|
||
font-weight: bold;
|
||
}
|
||
label {
|
||
margin-right: 10px;
|
||
}
|
||
.affix-card {
|
||
border: 2px dashed #aaa;
|
||
padding: 10px;
|
||
margin-bottom: 20px;
|
||
background: #fff;
|
||
}
|
||
.condition, .modifier {
|
||
padding: 6px;
|
||
margin: 5px 0;
|
||
border: 1px dashed #aaa;
|
||
background: #fafafa;
|
||
}
|
||
.button-container {
|
||
margin-top: 10px;
|
||
}
|
||
.affix-header {
|
||
font-weight: bold;
|
||
margin-bottom: 5px;
|
||
background: #eee;
|
||
padding: 5px;
|
||
}
|
||
.affix-body {
|
||
padding: 5px 10px;
|
||
}
|
||
#import-json-container {
|
||
margin: 10px 0;
|
||
}
|
||
#import-json-area {
|
||
width: 100%;
|
||
height: 100px;
|
||
}
|
||
#output {
|
||
white-space: pre-wrap;
|
||
background: #f0f0f0;
|
||
padding: 10px;
|
||
border: 1px solid #ccc;
|
||
min-height: 150px;
|
||
max-height: 300px;
|
||
overflow: auto;
|
||
}
|
||
button {
|
||
cursor: pointer;
|
||
}
|
||
</style>
|
||
</head>
|
||
|
||
<body>
|
||
<h1>多词条 Affix Editor</h1>
|
||
|
||
<!-- ======= 导入 JSON ======= -->
|
||
<fieldset id="import-json-container">
|
||
<legend>导入已有JSON</legend>
|
||
<p>在下方文本框粘贴已有的多Affix JSON,然后点击“导入”:</p>
|
||
<textarea id="import-json-area" placeholder></textarea>
|
||
<br />
|
||
<button type="button" onclick="importAffixes()">导入</button>
|
||
</fieldset>
|
||
|
||
<!-- ======= Affix 列表区域 ======= -->
|
||
<div id="affixes-list"></div>
|
||
|
||
<!-- ======= 新增 Affix、导出 JSON ======= -->
|
||
<div class="button-container">
|
||
<button type="button" onclick="createAffixCard()">+ 新建词条</button>
|
||
<button type="button" onclick="generateAllJSON()">生成 JSON</button>
|
||
</div>
|
||
<pre id="output"></pre>
|
||
|
||
<script>
|
||
/****************************************************************
|
||
* 1. 准备:变量、列表、工具函数
|
||
****************************************************************/
|
||
|
||
// 1.1 基础表达式变量
|
||
const baseVarList = [
|
||
{ value: 'level', label: '#level' },
|
||
{ value: 'health', label: '#health' },
|
||
{ value: 'time', label: '#time' },
|
||
{ value: 'food', label: '#food' },
|
||
{ value: 'light', label: '#light' },
|
||
{ value: 'oxygen', label: '#oxygen' },
|
||
{ value: 'armor_value', label: '#armor_value' },
|
||
{ value: 'dimension', label: '#dimension' },
|
||
{ value: 'rain', label: '#rain' },
|
||
{ value: 'hand_item', label: '#hand_item' }
|
||
];
|
||
|
||
// 1.2 EntityType -> kill_... 变量
|
||
const entityTypeNames = [
|
||
"ITEM","EXPERIENCE_ORB","AREA_EFFECT_CLOUD","ELDER_GUARDIAN","WITHER_SKELETON","STRAY","EGG",
|
||
"LEASH_KNOT","PAINTING","ARROW","SNOWBALL","FIREBALL","SMALL_FIREBALL","ENDER_PEARL","EYE_OF_ENDER",
|
||
"POTION","EXPERIENCE_BOTTLE","ITEM_FRAME","WITHER_SKULL","TNT","FALLING_BLOCK","FIREWORK_ROCKET",
|
||
"HUSK","SPECTRAL_ARROW","SHULKER_BULLET","DRAGON_FIREBALL","ZOMBIE_VILLAGER","SKELETON_HORSE",
|
||
"ZOMBIE_HORSE","ARMOR_STAND","DONKEY","MULE","EVOKER_FANGS","EVOKER","VEX","VINDICATOR","ILLUSIONER",
|
||
"COMMAND_BLOCK_MINECART","MINECART","CHEST_MINECART","FURNACE_MINECART","TNT_MINECART","HOPPER_MINECART",
|
||
"SPAWNER_MINECART","CREEPER","SKELETON","SPIDER","GIANT","ZOMBIE","SLIME","GHAST","ZOMBIFIED_PIGLIN",
|
||
"ENDERMAN","CAVE_SPIDER","SILVERFISH","BLAZE","MAGMA_CUBE","ENDER_DRAGON","WITHER","BAT","WITCH",
|
||
"ENDERMITE","GUARDIAN","SHULKER","PIG","SHEEP","COW","CHICKEN","SQUID","WOLF","MOOSHROOM","SNOW_GOLEM",
|
||
"OCELOT","IRON_GOLEM","HORSE","RABBIT","POLAR_BEAR","LLAMA","LLAMA_SPIT","PARROT","VILLAGER","END_CRYSTAL",
|
||
"TURTLE","PHANTOM","TRIDENT","COD","SALMON","PUFFERFISH","TROPICAL_FISH","DROWNED","DOLPHIN","CAT","PANDA",
|
||
"PILLAGER","RAVAGER","TRADER_LLAMA","WANDERING_TRADER","FOX","BEE","HOGLIN","PIGLIN","STRIDER","ZOGLIN",
|
||
"PIGLIN_BRUTE","AXOLOTL","GLOW_ITEM_FRAME","GLOW_SQUID","GOAT","MARKER","ALLAY","FROG","TADPOLE","WARDEN",
|
||
"CAMEL","BLOCK_DISPLAY","INTERACTION","ITEM_DISPLAY","SNIFFER","TEXT_DISPLAY","BREEZE","WIND_CHARGE",
|
||
"BREEZE_WIND_CHARGE","ARMADILLO","BOGGED","OMINOUS_ITEM_SPAWNER","ACACIA_BOAT","ACACIA_CHEST_BOAT",
|
||
"BAMBOO_RAFT","BAMBOO_CHEST_RAFT","BIRCH_BOAT","BIRCH_CHEST_BOAT","CHERRY_BOAT","CHERRY_CHEST_BOAT",
|
||
"DARK_OAK_BOAT","DARK_OAK_CHEST_BOAT","JUNGLE_BOAT","JUNGLE_CHEST_BOAT","MANGROVE_BOAT","MANGROVE_CHEST_BOAT",
|
||
"OAK_BOAT","OAK_CHEST_BOAT","PALE_OAK_BOAT","PALE_OAK_CHEST_BOAT","SPRUCE_BOAT","SPRUCE_CHEST_BOAT",
|
||
"CREAKING","FISHING_BOBBER","LIGHTNING_BOLT","PLAYER"
|
||
];
|
||
const killVarList = entityTypeNames.map(et => {
|
||
return { value: 'kill_'+et.toLowerCase(), label: '#kill_'+et.toLowerCase() };
|
||
});
|
||
const expressionVarList = [...baseVarList, ...killVarList];
|
||
|
||
// 1.3 Attributes
|
||
const attributeList = [
|
||
{ value: "max_health", label: "MAX_HEALTH" },
|
||
{ value: "follow_range", label: "FOLLOW_RANGE" },
|
||
{ value: "knockback_resistance", label: "KNOCKBACK_RESISTANCE" },
|
||
{ value: "movement_speed", label: "MOVEMENT_SPEED" },
|
||
{ value: "flying_speed", label: "FLYING_SPEED" },
|
||
{ value: "attack_damage", label: "ATTACK_DAMAGE" },
|
||
{ value: "attack_knockback", label: "ATTACK_KNOCKBACK" },
|
||
{ value: "attack_speed", label: "ATTACK_SPEED" },
|
||
{ value: "armor", label: "ARMOR" },
|
||
{ value: "armor_toughness", label: "ARMOR_TOUGHNESS" },
|
||
{ value: "fall_damage_multiplier", label: "FALL_DAMAGE_MULTIPLIER" },
|
||
{ value: "luck", label: "LUCK" },
|
||
{ value: "max_absorption", label: "MAX_ABSORPTION" },
|
||
{ value: "safe_fall_distance", label: "SAFE_FALL_DISTANCE" },
|
||
{ value: "scale", label: "SCALE" },
|
||
{ value: "step_height", label: "STEP_HEIGHT" },
|
||
{ value: "gravity", label: "GRAVITY" },
|
||
{ value: "jump_strength", label: "JUMP_STRENGTH" },
|
||
{ value: "burning_time", label: "BURNING_TIME" },
|
||
{ value: "explosion_knockback_resistance", label: "EXPLOSION_KNOCKBACK_RESISTANCE" },
|
||
{ value: "movement_efficiency", label: "MOVEMENT_EFFICIENCY" },
|
||
{ value: "oxygen_bonus", label: "OXYGEN_BONUS" },
|
||
{ value: "water_movement_efficiency", label: "WATER_MOVEMENT_EFFICIENCY" },
|
||
{ value: "tempt_range", label: "TEMPT_RANGE" },
|
||
{ value: "block_interaction_range", label: "BLOCK_INTERACTION_RANGE" },
|
||
{ value: "entity_interaction_range", label: "ENTITY_INTERACTION_RANGE" },
|
||
{ value: "block_break_speed", label: "BLOCK_BREAK_SPEED" },
|
||
{ value: "mining_efficiency", label: "MINING_EFFICIENCY" },
|
||
{ value: "sneaking_speed", label: "SNEAKING_SPEED" },
|
||
{ value: "submerged_mining_speed", label: "SUBMERGED_MINING_SPEED" },
|
||
{ value: "sweeping_damage_ratio", label: "SWEEPING_DAMAGE_RATIO" },
|
||
{ value: "spawn_reinforcements", label: "SPAWN_REINFORCEMENTS" }
|
||
];
|
||
|
||
// 1.4 PotionEffects
|
||
const potionEffectList = [
|
||
{ value: "speed", label: "SPEED" },
|
||
{ value: "slowness", label: "SLOWNESS" },
|
||
{ value: "haste", label: "HASTE" },
|
||
{ value: "mining_fatigue", label: "MINING_FATIGUE" },
|
||
{ value: "strength", label: "STRENGTH" },
|
||
{ value: "instant_health", label: "INSTANT_HEALTH" },
|
||
{ value: "instant_damage", label: "INSTANT_DAMAGE" },
|
||
{ value: "jump_boost", label: "JUMP_BOOST" },
|
||
{ value: "nausea", label: "NAUSEA" },
|
||
{ value: "regeneration", label: "REGENERATION" },
|
||
{ value: "resistance", label: "RESISTANCE" },
|
||
{ value: "fire_resistance", label: "FIRE_RESISTANCE" },
|
||
{ value: "water_breathing", label: "WATER_BREATHING" },
|
||
{ value: "invisibility", label: "INVISIBILITY" },
|
||
{ value: "blindness", label: "BLINDNESS" },
|
||
{ value: "night_vision", label: "NIGHT_VISION" },
|
||
{ value: "hunger", label: "HUNGER" },
|
||
{ value: "weakness", label: "WEAKNESS" },
|
||
{ value: "poison", label: "POISON" },
|
||
{ value: "wither", label: "WITHER" },
|
||
{ value: "health_boost", label: "HEALTH_BOOST" },
|
||
{ value: "absorption", label: "ABSORPTION" },
|
||
{ value: "saturation", label: "SATURATION" },
|
||
{ value: "glowing", label: "GLOWING" },
|
||
{ value: "levitation", label: "LEVITATION" },
|
||
{ value: "luck", label: "LUCK" },
|
||
{ value: "unluck", label: "UNLUCK" },
|
||
{ value: "slow_falling", label: "SLOW_FALLING" },
|
||
{ value: "conduit_power", label: "CONDUIT_POWER" },
|
||
{ value: "dolphins_grace", label: "DOLPHINS_GRACE" },
|
||
{ value: "bad_omen", label: "BAD_OMEN" },
|
||
{ value: "hero_of_the_village", label: "HERO_OF_THE_VILLAGE" },
|
||
{ value: "darkness", label: "DARKNESS" },
|
||
{ value: "trial_omen", label: "TRIAL_OMEN" },
|
||
{ value: "raid_omen", label: "RAID_OMEN" },
|
||
{ value: "wind_charged", label: "WIND_CHARGED" },
|
||
{ value: "weaving", label: "WEAVING" },
|
||
{ value: "oozing", label: "OOZING" },
|
||
{ value: "infested", label: "INFESTED" }
|
||
];
|
||
|
||
// 1.5 Biome
|
||
const biomeList = [
|
||
{ value: "ocean", label: "OCEAN" },
|
||
{ value: "plains", label: "PLAINS" },
|
||
{ value: "desert", label: "DESERT" },
|
||
{ value: "windswept_hills", label: "WINDSWEPT_HILLS" },
|
||
{ value: "forest", label: "FOREST" },
|
||
{ value: "taiga", label: "TAIGA" },
|
||
{ value: "swamp", label: "SWAMP" },
|
||
{ value: "mangrove_swamp", label: "MANGROVE_SWAMP" },
|
||
{ value: "river", label: "RIVER" },
|
||
{ value: "nether_wastes", label: "NETHER_WASTES" },
|
||
{ value: "the_end", label: "THE_END" },
|
||
{ value: "frozen_ocean", label: "FROZEN_OCEAN" },
|
||
{ value: "frozen_river", label: "FROZEN_RIVER" },
|
||
{ value: "snowy_plains", label: "SNOWY_PLAINS" },
|
||
{ value: "mushroom_fields", label: "MUSHROOM_FIELDS" },
|
||
{ value: "beach", label: "BEACH" },
|
||
{ value: "jungle", label: "JUNGLE" },
|
||
{ value: "sparse_jungle", label: "SPARSE_JUNGLE" },
|
||
{ value: "deep_ocean", label: "DEEP_OCEAN" },
|
||
{ value: "stony_shore", label: "STONY_SHORE" },
|
||
{ value: "snowy_beach", label: "SNOWY_BEACH" },
|
||
{ value: "birch_forest", label: "BIRCH_FOREST" },
|
||
{ value: "dark_forest", label: "DARK_FOREST" },
|
||
{ value: "pale_garden", label: "PALE_GARDEN" },
|
||
{ value: "snowy_taiga", label: "SNOWY_TAIGA" },
|
||
{ value: "old_growth_pine_taiga", label: "OLD_GROWTH_PINE_TAIGA" },
|
||
{ value: "windswept_forest", label: "WINDSWEPT_FOREST" },
|
||
{ value: "savanna", label: "SAVANNA" },
|
||
{ value: "savanna_plateau", label: "SAVANNA_PLATEAU" },
|
||
{ value: "badlands", label: "BADLANDS" },
|
||
{ value: "wooded_badlands", label: "WOODED_BADLANDS" },
|
||
{ value: "small_end_islands", label: "SMALL_END_ISLANDS" },
|
||
{ value: "end_midlands", label: "END_MIDLANDS" },
|
||
{ value: "end_highlands", label: "END_HIGHLANDS" },
|
||
{ value: "end_barrens", label: "END_BARRENS" },
|
||
{ value: "warm_ocean", label: "WARM_OCEAN" },
|
||
{ value: "lukewarm_ocean", label: "LUKEWARM_OCEAN" },
|
||
{ value: "cold_ocean", label: "COLD_OCEAN" },
|
||
{ value: "deep_lukewarm_ocean", label: "DEEP_LUKEWARM_OCEAN" },
|
||
{ value: "deep_cold_ocean", label: "DEEP_COLD_OCEAN" },
|
||
{ value: "deep_frozen_ocean", label: "DEEP_FROZEN_OCEAN" },
|
||
{ value: "the_void", label: "THE_VOID" },
|
||
{ value: "sunflower_plains", label: "SUNFLOWER_PLAINS" },
|
||
{ value: "windswept_gravelly_hills", label: "WINDSWEPT_GRAVELLY_HILLS" },
|
||
{ value: "flower_forest", label: "FLOWER_FOREST" },
|
||
{ value: "ice_spikes", label: "ICE_SPIKES" },
|
||
{ value: "old_growth_birch_forest", label: "OLD_GROWTH_BIRCH_FOREST" },
|
||
{ value: "old_growth_spruce_taiga", label: "OLD_GROWTH_SPRUCE_TAIGA" },
|
||
{ value: "windswept_savanna", label: "WINDSWEPT_SAVANNA" },
|
||
{ value: "eroded_badlands", label: "ERODED_BADLANDS" },
|
||
{ value: "bamboo_jungle", label: "BAMBOO_JUNGLE" },
|
||
{ value: "soul_sand_valley", label: "SOUL_SAND_VALLEY" },
|
||
{ value: "crimson_forest", label: "CRIMSON_FOREST" },
|
||
{ value: "warped_forest", label: "WARPED_FOREST" },
|
||
{ value: "basalt_deltas", label: "BASALT_DELTAS" },
|
||
{ value: "dripstone_caves", label: "DRIPSTONE_CAVES" },
|
||
{ value: "lush_caves", label: "LUSH_CAVES" },
|
||
{ value: "deep_dark", label: "DEEP_DARK" },
|
||
{ value: "meadow", label: "MEADOW" },
|
||
{ value: "grove", label: "GROVE" },
|
||
{ value: "snowy_slopes", label: "SNOWY_SLOPES" },
|
||
{ value: "frozen_peaks", label: "FROZEN_PEAKS" },
|
||
{ value: "jagged_peaks", label: "JAGGED_PEAKS" },
|
||
{ value: "stony_peaks", label: "STONY_PEAKS" },
|
||
{ value: "cherry_grove", label: "CHERRY_GROVE" }
|
||
];
|
||
|
||
// 1.6 一个工具函数:从 JSON 中安全地构造 object
|
||
function safeParseJSON(text) {
|
||
try {
|
||
return JSON.parse(text);
|
||
} catch(e) {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
|
||
/****************************************************************
|
||
* 2. 逻辑:创建 Affix 面板 / 导入 / 输出
|
||
****************************************************************/
|
||
|
||
// ========== 导入 JSON ==========
|
||
function importAffixes() {
|
||
const text = document.getElementById('import-json-area').value.trim();
|
||
if (!text) return;
|
||
const obj = safeParseJSON(text);
|
||
if (!obj) {
|
||
alert("JSON 解析失败,请检查格式!");
|
||
return;
|
||
}
|
||
// obj 形如 { "affixId": {...}, "another_affix": {...} }
|
||
// 遍历每个 key 并创建相应的面板
|
||
for (const [affixId, affixData] of Object.entries(obj)) {
|
||
createAffixCard(affixData);
|
||
}
|
||
alert("已导入 " + Object.keys(obj).length + " 个词条。");
|
||
}
|
||
|
||
// ========== 创建新的词条面板 ==========
|
||
function createAffixCard(importData = null) {
|
||
// importData 若非空,则使用其值初始化;否则创建空白
|
||
const affixListContainer = document.getElementById('affixes-list');
|
||
|
||
// 整个 affix-card
|
||
const card = document.createElement('div');
|
||
card.className = 'affix-card';
|
||
|
||
// Header
|
||
const header = document.createElement('div');
|
||
header.className = 'affix-header';
|
||
header.textContent = '词条信息';
|
||
card.appendChild(header);
|
||
|
||
// Body
|
||
const body = document.createElement('div');
|
||
body.className = 'affix-body';
|
||
card.appendChild(body);
|
||
|
||
// 基础信息
|
||
const fieldsetBase = document.createElement('fieldset');
|
||
const legendBase = document.createElement('legend');
|
||
legendBase.textContent = '基础信息';
|
||
fieldsetBase.appendChild(legendBase);
|
||
|
||
// ID
|
||
const p1 = document.createElement('p');
|
||
const labelId = document.createElement('label');
|
||
labelId.textContent = '词条ID: ';
|
||
const inputId = document.createElement('input');
|
||
inputId.type = 'text';
|
||
inputId.placeholder = '如: berserker_mode';
|
||
labelId.appendChild(inputId);
|
||
|
||
// Quality
|
||
const labelQuality = document.createElement('label');
|
||
labelQuality.textContent = ' 品质: ';
|
||
const selectQuality = document.createElement('select');
|
||
selectQuality.innerHTML = `
|
||
<option value="COMMON">COMMON</option>
|
||
<option value="RARE">RARE</option>
|
||
<option value="EPIC">EPIC</option>
|
||
<option value="LEGENDARY">LEGENDARY</option>
|
||
`;
|
||
labelQuality.appendChild(selectQuality);
|
||
|
||
// Type
|
||
const labelType = document.createElement('label');
|
||
labelType.textContent = ' 类型: ';
|
||
const selectType = document.createElement('select');
|
||
selectType.innerHTML = `
|
||
<option value="POSITIVE">POSITIVE</option>
|
||
<option value="NEGATIVE">NEGATIVE</option>
|
||
`;
|
||
labelType.appendChild(selectType);
|
||
|
||
p1.appendChild(labelId);
|
||
p1.appendChild(labelQuality);
|
||
p1.appendChild(labelType);
|
||
|
||
fieldsetBase.appendChild(p1);
|
||
|
||
// desc
|
||
const p2 = document.createElement('p');
|
||
const labelDesc = document.createElement('label');
|
||
labelDesc.textContent = '描述: ';
|
||
const textDesc = document.createElement('textarea');
|
||
textDesc.rows = 2;
|
||
textDesc.cols = 50;
|
||
textDesc.placeholder = '该词条的简短描述...';
|
||
labelDesc.appendChild(textDesc);
|
||
p2.appendChild(labelDesc);
|
||
fieldsetBase.appendChild(p2);
|
||
|
||
// interval & logic
|
||
const p3 = document.createElement('p');
|
||
const labelInt = document.createElement('label');
|
||
labelInt.textContent = '检测间隔(秒): ';
|
||
const inputInt = document.createElement('input');
|
||
inputInt.type = 'number';
|
||
inputInt.value = 5;
|
||
labelInt.appendChild(inputInt);
|
||
|
||
const labelLogic = document.createElement('label');
|
||
labelLogic.textContent = ' 条件逻辑: ';
|
||
const selectLogic = document.createElement('select');
|
||
selectLogic.innerHTML = `
|
||
<option value="AND">AND</option>
|
||
<option value="OR">OR</option>
|
||
`;
|
||
labelLogic.appendChild(selectLogic);
|
||
|
||
p3.appendChild(labelInt);
|
||
p3.appendChild(labelLogic);
|
||
fieldsetBase.appendChild(p3);
|
||
|
||
body.appendChild(fieldsetBase);
|
||
|
||
// 条件
|
||
const fieldsetCond = document.createElement('fieldset');
|
||
const legendCond = document.createElement('legend');
|
||
legendCond.textContent = '条件(Conditions)';
|
||
fieldsetCond.appendChild(legendCond);
|
||
const condContainer = document.createElement('div');
|
||
fieldsetCond.appendChild(condContainer);
|
||
|
||
const condBtnDiv = document.createElement('div');
|
||
condBtnDiv.className = 'button-container';
|
||
const condBtn = document.createElement('button');
|
||
condBtn.type = 'button';
|
||
condBtn.textContent = '+ 添加条件';
|
||
condBtn.onclick = () => addConditionDiv(condContainer);
|
||
condBtnDiv.appendChild(condBtn);
|
||
fieldsetCond.appendChild(condBtnDiv);
|
||
|
||
body.appendChild(fieldsetCond);
|
||
|
||
// 修饰符
|
||
const fieldsetMod = document.createElement('fieldset');
|
||
const legendMod = document.createElement('legend');
|
||
legendMod.textContent = '修饰符(Modifiers)';
|
||
fieldsetMod.appendChild(legendMod);
|
||
const modContainer = document.createElement('div');
|
||
fieldsetMod.appendChild(modContainer);
|
||
|
||
const modBtnDiv = document.createElement('div');
|
||
modBtnDiv.className = 'button-container';
|
||
const modBtn = document.createElement('button');
|
||
modBtn.type = 'button';
|
||
modBtn.textContent = '+ 添加修饰符';
|
||
modBtn.onclick = () => addModifierDiv(modContainer);
|
||
modBtnDiv.appendChild(modBtn);
|
||
fieldsetMod.appendChild(modBtnDiv);
|
||
|
||
body.appendChild(fieldsetMod);
|
||
|
||
// 删除词条
|
||
const delBtn = document.createElement('button');
|
||
delBtn.textContent = '删除该词条';
|
||
delBtn.type = 'button';
|
||
delBtn.style.marginTop = '10px';
|
||
delBtn.onclick = () => affixListContainer.removeChild(card);
|
||
body.appendChild(delBtn);
|
||
|
||
affixListContainer.appendChild(card);
|
||
|
||
// 如果有 importData,就初始化
|
||
if (importData) {
|
||
inputId.value = importData.id || '';
|
||
selectQuality.value = importData.quality || 'COMMON';
|
||
selectType.value = importData.type || 'POSITIVE';
|
||
textDesc.value = importData.desc || '';
|
||
inputInt.value = importData.checkInterval || 5;
|
||
selectLogic.value = importData.conditionLogic || 'AND';
|
||
|
||
// 条件
|
||
if (Array.isArray(importData.conditions)) {
|
||
importData.conditions.forEach(c => {
|
||
addConditionDiv(condContainer, c);
|
||
});
|
||
}
|
||
// 修饰符
|
||
if (Array.isArray(importData.modifiers)) {
|
||
importData.modifiers.forEach(m => {
|
||
addModifierDiv(modContainer, m);
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
// ========== 添加一个 condition div ==========
|
||
function addConditionDiv(container, initData = null) {
|
||
const div = document.createElement('div');
|
||
div.className = 'condition';
|
||
|
||
const sel = document.createElement('select');
|
||
sel.innerHTML = `
|
||
<option value="EXPRESSION">EXPRESSION</option>
|
||
<option value="PLAYER_LEVEL">PLAYER_LEVEL</option>
|
||
<option value="ITEM_EQUIPPED">ITEM_EQUIPPED</option>
|
||
<option value="WORLD_TIME">WORLD_TIME</option>
|
||
<option value="BIOME">BIOME</option>
|
||
<option value="WEATHER">WEATHER</option>
|
||
<option value="MOB_KILLS">MOB_KILLS</option>
|
||
`;
|
||
sel.onchange = () => updateConditionFields(div, sel.value);
|
||
|
||
const labelType = document.createElement('label');
|
||
labelType.textContent = '类型: ';
|
||
labelType.appendChild(sel);
|
||
|
||
const removeBtn = document.createElement('button');
|
||
removeBtn.textContent = '删除条件';
|
||
removeBtn.type = 'button';
|
||
removeBtn.onclick = () => container.removeChild(div);
|
||
|
||
const fieldsContainer = document.createElement('div');
|
||
fieldsContainer.className = 'condition-fields';
|
||
|
||
div.appendChild(labelType);
|
||
div.appendChild(document.createElement('br'));
|
||
div.appendChild(fieldsContainer);
|
||
div.appendChild(document.createElement('br'));
|
||
div.appendChild(removeBtn);
|
||
|
||
container.appendChild(div);
|
||
|
||
// 默认
|
||
updateConditionFields(div, 'EXPRESSION');
|
||
// 如果有 initData,设置
|
||
if (initData) {
|
||
sel.value = initData.type || 'EXPRESSION';
|
||
updateConditionFields(div, sel.value);
|
||
// 填充值
|
||
for (const [k,v] of Object.entries(initData)) {
|
||
if (k === 'type') continue;
|
||
const node = fieldsContainer.querySelector(`[data-field="${k}"]`);
|
||
if (node) node.value = v;
|
||
}
|
||
}
|
||
}
|
||
|
||
function updateConditionFields(conditionDiv, conditionType) {
|
||
const fieldsContainer = conditionDiv.querySelector('.condition-fields');
|
||
fieldsContainer.innerHTML = '';
|
||
|
||
switch(conditionType) {
|
||
case 'EXPRESSION': {
|
||
const labelExpr = document.createElement('label');
|
||
labelExpr.textContent = '表达式: ';
|
||
const inputExpr = document.createElement('input');
|
||
inputExpr.type = 'text';
|
||
inputExpr.placeholder = '#level >= 10 或 #kill_zombie > 20';
|
||
inputExpr.dataset.field = 'expression';
|
||
labelExpr.appendChild(inputExpr);
|
||
|
||
// 可插入变量
|
||
const varLabel = document.createElement('label');
|
||
varLabel.textContent = '可用变量: ';
|
||
const varSelect = document.createElement('select');
|
||
expressionVarList.forEach(v => {
|
||
const opt = document.createElement('option');
|
||
opt.value = v.value;
|
||
opt.textContent = v.label;
|
||
varSelect.appendChild(opt);
|
||
});
|
||
const varBtn = document.createElement('button');
|
||
varBtn.textContent = '插入';
|
||
varBtn.type = 'button';
|
||
varBtn.onclick = () => {
|
||
const chosenVar = varSelect.value;
|
||
inputExpr.value += `#${chosenVar} `;
|
||
inputExpr.focus();
|
||
};
|
||
|
||
fieldsContainer.appendChild(labelExpr);
|
||
fieldsContainer.appendChild(document.createElement('br'));
|
||
fieldsContainer.appendChild(varLabel);
|
||
varLabel.appendChild(varSelect);
|
||
fieldsContainer.appendChild(varBtn);
|
||
break;
|
||
}
|
||
case 'PLAYER_LEVEL': {
|
||
const labelLevel = document.createElement('label');
|
||
labelLevel.textContent = '最低等级: ';
|
||
const inputLevel = document.createElement('input');
|
||
inputLevel.type = 'number';
|
||
inputLevel.placeholder = '10';
|
||
inputLevel.dataset.field = 'level';
|
||
labelLevel.appendChild(inputLevel);
|
||
fieldsContainer.appendChild(labelLevel);
|
||
break;
|
||
}
|
||
case 'ITEM_EQUIPPED': {
|
||
const labelItem = document.createElement('label');
|
||
labelItem.textContent = '物品: ';
|
||
const inputItem = document.createElement('input');
|
||
inputItem.type = 'text';
|
||
inputItem.placeholder = 'DIAMOND_CHESTPLATE';
|
||
inputItem.dataset.field = 'item';
|
||
labelItem.appendChild(inputItem);
|
||
fieldsContainer.appendChild(labelItem);
|
||
break;
|
||
}
|
||
case 'WORLD_TIME': {
|
||
const labelMin = document.createElement('label');
|
||
labelMin.textContent = '时间下限: ';
|
||
const inputMin = document.createElement('input');
|
||
inputMin.type = 'number';
|
||
inputMin.placeholder = '13000';
|
||
inputMin.dataset.field = 'min';
|
||
labelMin.appendChild(inputMin);
|
||
|
||
const labelMax = document.createElement('label');
|
||
labelMax.textContent = ' 时间上限: ';
|
||
const inputMax = document.createElement('input');
|
||
inputMax.type = 'number';
|
||
inputMax.placeholder = '23000';
|
||
inputMax.dataset.field = 'max';
|
||
labelMax.appendChild(inputMax);
|
||
|
||
fieldsContainer.appendChild(labelMin);
|
||
fieldsContainer.appendChild(labelMax);
|
||
break;
|
||
}
|
||
case 'BIOME': {
|
||
const labelB = document.createElement('label');
|
||
labelB.textContent = '生物群系: ';
|
||
const selB = document.createElement('select');
|
||
selB.dataset.field = 'biome';
|
||
biomeList.forEach(b => {
|
||
const opt = document.createElement('option');
|
||
opt.value = b.value;
|
||
opt.textContent = b.label;
|
||
selB.appendChild(opt);
|
||
});
|
||
labelB.appendChild(selB);
|
||
fieldsContainer.appendChild(labelB);
|
||
break;
|
||
}
|
||
case 'WEATHER': {
|
||
const labelW = document.createElement('label');
|
||
labelW.textContent = '下雨: ';
|
||
const selW = document.createElement('select');
|
||
selW.dataset.field = 'weather';
|
||
selW.innerHTML = `
|
||
<option value="true">true</option>
|
||
<option value="false">false</option>
|
||
`;
|
||
labelW.appendChild(selW);
|
||
fieldsContainer.appendChild(labelW);
|
||
break;
|
||
}
|
||
case 'MOB_KILLS': {
|
||
const labelMob = document.createElement('label');
|
||
labelMob.textContent = '怪物类型: ';
|
||
const inputMob = document.createElement('input');
|
||
inputMob.type = 'text';
|
||
inputMob.placeholder = 'ZOMBIE';
|
||
inputMob.dataset.field = 'mob';
|
||
labelMob.appendChild(inputMob);
|
||
|
||
const labelKills = document.createElement('label');
|
||
labelKills.textContent = ' 杀怪数量: ';
|
||
const inputKills = document.createElement('input');
|
||
inputKills.type = 'number';
|
||
inputKills.placeholder = '50';
|
||
inputKills.dataset.field = 'kills';
|
||
labelKills.appendChild(inputKills);
|
||
|
||
fieldsContainer.appendChild(labelMob);
|
||
fieldsContainer.appendChild(labelKills);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// ========== 添加一个 modifier div ==========
|
||
function addModifierDiv(container, initData = null) {
|
||
const div = document.createElement('div');
|
||
div.className = 'modifier';
|
||
|
||
const sel = document.createElement('select');
|
||
sel.innerHTML = `
|
||
<option value="ATTRIBUTE">ATTRIBUTE</option>
|
||
<option value="POTION">POTION</option>
|
||
`;
|
||
sel.onchange = () => updateModifierFields(div, sel.value);
|
||
|
||
const labelType = document.createElement('label');
|
||
labelType.textContent = '类型: ';
|
||
labelType.appendChild(sel);
|
||
|
||
const removeBtn = document.createElement('button');
|
||
removeBtn.textContent = '删除修饰符';
|
||
removeBtn.type = 'button';
|
||
removeBtn.onclick = () => container.removeChild(div);
|
||
|
||
const fieldsContainer = document.createElement('div');
|
||
fieldsContainer.className = 'modifier-fields';
|
||
|
||
div.appendChild(labelType);
|
||
div.appendChild(document.createElement('br'));
|
||
div.appendChild(fieldsContainer);
|
||
div.appendChild(document.createElement('br'));
|
||
div.appendChild(removeBtn);
|
||
|
||
container.appendChild(div);
|
||
|
||
updateModifierFields(div, 'ATTRIBUTE');
|
||
if (initData) {
|
||
sel.value = initData.type || 'ATTRIBUTE';
|
||
updateModifierFields(div, sel.value);
|
||
for (const [k,v] of Object.entries(initData)) {
|
||
if (k === 'type') continue;
|
||
const node = fieldsContainer.querySelector(`[data-field="${k}"]`);
|
||
if (node) node.value = v;
|
||
}
|
||
}
|
||
}
|
||
|
||
function updateModifierFields(modDiv, modType) {
|
||
const fieldsContainer = modDiv.querySelector('.modifier-fields');
|
||
fieldsContainer.innerHTML = '';
|
||
|
||
if (modType === 'ATTRIBUTE') {
|
||
const labelAttr = document.createElement('label');
|
||
labelAttr.textContent = '属性: ';
|
||
const selAttr = document.createElement('select');
|
||
selAttr.dataset.field = 'attribute';
|
||
attributeList.forEach(att => {
|
||
const opt = document.createElement('option');
|
||
opt.value = att.value;
|
||
opt.textContent = att.label;
|
||
selAttr.appendChild(opt);
|
||
});
|
||
labelAttr.appendChild(selAttr);
|
||
|
||
const labelKey = document.createElement('label');
|
||
labelKey.textContent = ' keyName: ';
|
||
const inputKey = document.createElement('input');
|
||
inputKey.type = 'text';
|
||
inputKey.placeholder = 'affix_key_name';
|
||
inputKey.dataset.field = 'keyName';
|
||
labelKey.appendChild(inputKey);
|
||
|
||
const labelOp = document.createElement('label');
|
||
labelOp.textContent = ' operation: ';
|
||
const selOp = document.createElement('select');
|
||
selOp.dataset.field = 'operation';
|
||
selOp.innerHTML = `
|
||
<option value="ADD_NUMBER">ADD_NUMBER</option>
|
||
<option value="ADD_SCALAR">ADD_SCALAR</option>
|
||
<option value="MULTIPLY_SCALAR_1">MULTIPLY_SCALAR_1</option>
|
||
`;
|
||
labelOp.appendChild(selOp);
|
||
|
||
const labelInfo = document.createElement('p');
|
||
labelInfo.textContent = '数值增益(填写 amount)或表达式增益(填写 expression)二选一:';
|
||
|
||
const labelAmt = document.createElement('label');
|
||
labelAmt.textContent = ' amount: ';
|
||
const inputAmt = document.createElement('input');
|
||
inputAmt.type = 'number';
|
||
inputAmt.step = '0.1';
|
||
inputAmt.placeholder = '5.0 或 0.2';
|
||
inputAmt.dataset.field = 'amount';
|
||
labelAmt.appendChild(inputAmt);
|
||
|
||
const labelExp = document.createElement('label');
|
||
labelExp.textContent = ' expression: ';
|
||
const inputExp = document.createElement('input');
|
||
inputExp.type = 'text';
|
||
inputExp.placeholder = '#kill_zombie * 1.5';
|
||
inputExp.dataset.field = 'expression';
|
||
labelExp.appendChild(inputExp);
|
||
|
||
// 表达式插入变量
|
||
const varLabel = document.createElement('label');
|
||
varLabel.textContent = '插入变量: ';
|
||
const varSel = document.createElement('select');
|
||
expressionVarList.forEach(v => {
|
||
const opt = document.createElement('option');
|
||
opt.value = v.value;
|
||
opt.textContent = v.label;
|
||
varSel.appendChild(opt);
|
||
});
|
||
const varBtn = document.createElement('button');
|
||
varBtn.textContent = '插入';
|
||
varBtn.type = 'button';
|
||
varBtn.onclick = () => {
|
||
const chosenVar = varSel.value;
|
||
inputExp.value += `#${chosenVar} `;
|
||
inputExp.focus();
|
||
};
|
||
|
||
fieldsContainer.appendChild(labelAttr);
|
||
fieldsContainer.appendChild(labelKey);
|
||
fieldsContainer.appendChild(labelOp);
|
||
fieldsContainer.appendChild(document.createElement('br'));
|
||
fieldsContainer.appendChild(labelInfo);
|
||
fieldsContainer.appendChild(labelAmt);
|
||
fieldsContainer.appendChild(labelExp);
|
||
fieldsContainer.appendChild(document.createElement('br'));
|
||
fieldsContainer.appendChild(varLabel);
|
||
varLabel.appendChild(varSel);
|
||
fieldsContainer.appendChild(varBtn);
|
||
|
||
} else if (modType === 'POTION') {
|
||
const labelEff = document.createElement('label');
|
||
labelEff.textContent = '效果类型(effect): ';
|
||
const selEff = document.createElement('select');
|
||
selEff.dataset.field = 'effect';
|
||
potionEffectList.forEach(pt => {
|
||
const opt = document.createElement('option');
|
||
opt.value = pt.value;
|
||
opt.textContent = pt.label;
|
||
selEff.appendChild(opt);
|
||
});
|
||
labelEff.appendChild(selEff);
|
||
|
||
const labelDur = document.createElement('label');
|
||
labelDur.textContent = ' 持续时间(ticks): ';
|
||
const inputDur = document.createElement('input');
|
||
inputDur.type = 'number';
|
||
inputDur.placeholder = '200 (约10秒)';
|
||
inputDur.dataset.field = 'duration';
|
||
labelDur.appendChild(inputDur);
|
||
|
||
const labelAmp = document.createElement('label');
|
||
labelAmp.textContent = ' 等级(amplifier): ';
|
||
const inputAmp = document.createElement('input');
|
||
inputAmp.type = 'number';
|
||
inputAmp.placeholder = '0 为一级';
|
||
inputAmp.dataset.field = 'amplifier';
|
||
labelAmp.appendChild(inputAmp);
|
||
|
||
fieldsContainer.appendChild(labelEff);
|
||
fieldsContainer.appendChild(labelDur);
|
||
fieldsContainer.appendChild(labelAmp);
|
||
}
|
||
}
|
||
|
||
// ========== 生成全部 JSON ==========
|
||
function generateAllJSON() {
|
||
const affixMap = {};
|
||
const affixCards = document.querySelectorAll('.affix-card');
|
||
affixCards.forEach(card => {
|
||
// 基础信息
|
||
const inputId = card.querySelector('input[type="text"]');
|
||
const selectQuality = card.querySelectorAll('select')[0];
|
||
const selectType = card.querySelectorAll('select')[1];
|
||
const textDesc = card.querySelector('textarea');
|
||
const inputInt = card.querySelector('input[type="number"]');
|
||
const selectLogic = card.querySelectorAll('select')[2];
|
||
|
||
const affixId = inputId.value.trim() || 'my_affix_' + Math.floor(Math.random()*10000);
|
||
const affixObj = {
|
||
id: affixId,
|
||
quality: selectQuality.value,
|
||
type: selectType.value,
|
||
desc: textDesc.value.trim(),
|
||
checkInterval: parseInt(inputInt.value, 10),
|
||
conditionLogic: selectLogic.value,
|
||
conditions: [],
|
||
modifiers: []
|
||
};
|
||
|
||
// 条件
|
||
const condContainer = card.querySelector('fieldset:nth-of-type(2) > div');
|
||
const condDivs = condContainer.querySelectorAll('.condition');
|
||
condDivs.forEach(cd => {
|
||
const selType = cd.querySelector('select');
|
||
const typeValue = selType.value;
|
||
const cobj = { type: typeValue };
|
||
const fieldNodes = cd.querySelectorAll('[data-field]');
|
||
fieldNodes.forEach(node => {
|
||
const k = node.dataset.field;
|
||
let val = node.value;
|
||
if (['min','max','level','kills'].includes(k)) {
|
||
val = parseInt(val, 10);
|
||
} else if (k === 'weather') {
|
||
val = (val === 'true');
|
||
}
|
||
if (val !== '') {
|
||
cobj[k] = val;
|
||
}
|
||
});
|
||
affixObj.conditions.push(cobj);
|
||
});
|
||
|
||
// 修饰符
|
||
const modContainer = card.querySelector('fieldset:nth-of-type(3) > div');
|
||
const modDivs = modContainer.querySelectorAll('.modifier');
|
||
modDivs.forEach(md => {
|
||
const selType = md.querySelector('select');
|
||
const typeValue = selType.value;
|
||
const mobj = { type: typeValue };
|
||
const fieldNodes = md.querySelectorAll('[data-field]');
|
||
fieldNodes.forEach(node => {
|
||
const k = node.dataset.field;
|
||
let val = node.value;
|
||
if (k === 'amount') {
|
||
val = parseFloat(val);
|
||
} else if (['duration','amplifier'].includes(k)) {
|
||
val = parseInt(val, 10);
|
||
}
|
||
if (val !== '') {
|
||
mobj[k] = val;
|
||
}
|
||
});
|
||
affixObj.modifiers.push(mobj);
|
||
});
|
||
|
||
affixMap[affixId] = affixObj;
|
||
});
|
||
|
||
document.getElementById('output').textContent = JSON.stringify(affixMap, null, 2);
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|