# lucky_lottery_fortune_system.gd —— Metin2 40250 官方幸运乐透刮刮乐与老妪占卜转运系统 1:1 # 100% 对照 40250 服务端 fortune_telling.quest, xxx_change_to_lotto.quest, questlua_pc.cpp (give_lotto) class_name LuckyLotteryFortuneSystem extends RefCounted signal fortune_told(tier: String, exp_bonus: float, gold_bonus: float, desc: String) signal talisman_acquired(item_vnum: int, cost: int) signal lottery_scratched(digits: Array, prize_tier: String, prize_gold: int, prize_item_vnum: int) signal system_error(reason: String) # NPC 与道具 Vnum 常量 const NPC_OLD_WOMAN := 9006 # 老妪 (Old Woman) const FORTUNE_COST_GOLD := 7777 # 占卜消耗金币 (40250 原版 7777) const TALISMAN_COST_GOLD := 15000 # 辟邪转运符费用 const VNUM_LOTTO_TICKET := 50027 # 乐透彩票奖券 (Lotterielos) const VNUM_LOTTO_BOOK := 50001 # 幸运奖券号 (Glücksbuch) const VNUM_TALISMAN := 70052 # 转运符咒 (Karma Talisman) const VNUM_BLESSING_SCROLL := 25040 # 祝福卷轴 (特等奖附赠) # 占卜卦象运势常量 const TIER_GREAT_LUCK := "great_luck" # 大吉 (+10% 经验, +10% 金币掉率) const TIER_GOOD_LUCK := "good_luck" # 中吉 (+5% 经验, +5% 金币掉率) const TIER_NORMAL := "normal" # 平 (无额外加成) const TIER_BAD_LUCK := "bad_luck" # 凶 (-10% 金币掉率) const FORTUNE_DESCRIPTIONS: Dictionary = { TIER_GREAT_LUCK: { "name": "【大吉·紫气东来】", "text": "老妪眯起双眼惊叹道:“祥云笼罩,鸿运当头!你今天的所有修行与征战都将得到福星的高照庇护!”", "exp_bonus": 0.10, "gold_bonus": 0.10 }, TIER_GOOD_LUCK: { "name": "【中吉·吉星照拂】", "text": "老妪慈祥地微笑道:“运势平稳向上,行事多有小顺,今日野外历练收获定然不错。”", "exp_bonus": 0.05, "gold_bonus": 0.05 }, TIER_NORMAL: { "name": "【平常·云淡风轻】", "text": "老妪看了看签文道:“波澜不惊,顺其自然。一切还需依靠你自身的勤勉苦练。”", "exp_bonus": 0.0, "gold_bonus": 0.0 }, TIER_BAD_LUCK: { "name": "【大凶·晦暗蒙尘】", "text": "老妪脸色微微一变:“乌云蔽日,近来出行需万分小心,财运恐有所折损!老身这里有一枚开光转运符……”", "exp_bonus": 0.0, "gold_bonus": -0.10 } } # 当前玩家占卜运势状态 var current_fortune_tier: String = "" var fortune_time_remaining: float = 0.0 # 40250 原版持续 8 小时 (28800 秒) var has_karma_talisman: bool = false var lotto_history: Array[Dictionary] = [] # ========================================== # 1. 老妪占卜祈福 (fortune_telling.quest) # ========================================== func tell_fortune( player_level: int, player_gold: int, forced_tier: String = "" ) -> Dictionary: # 等级需达到 20 级 if player_level <= 20: system_error.emit("LEVEL_TOO_LOW") return { "ok": false, "reason": "LEVEL_TOO_LOW", "msg": "老妪摇了摇头:“年轻的冒险者,你还不满 20 级,未来的命运尚在凝聚之中,现在还不是占卜的时候。”" } # 检查金币 if player_gold < FORTUNE_COST_GOLD: system_error.emit("NOT_ENOUGH_GOLD") return { "ok": false, "reason": "NOT_ENOUGH_GOLD", "msg": "占卜诚意金不足!老妪需要 %d 枚金币作为供奉卦金。" % FORTUNE_COST_GOLD } var remaining_gold = player_gold - FORTUNE_COST_GOLD # 判定运势 var picked_tier = "" if forced_tier != "" and FORTUNE_DESCRIPTIONS.has(forced_tier): picked_tier = forced_tier else: var r = randi_range(1, 100) if r <= 20: picked_tier = TIER_GREAT_LUCK elif r <= 50: picked_tier = TIER_GOOD_LUCK elif r <= 80: picked_tier = TIER_NORMAL else: picked_tier = TIER_BAD_LUCK current_fortune_tier = picked_tier fortune_time_remaining = 28800.0 # 8 小时 has_karma_talisman = false var info = FORTUNE_DESCRIPTIONS[picked_tier] var can_buy_talisman = (picked_tier == TIER_BAD_LUCK or picked_tier == TIER_NORMAL) fortune_told.emit(picked_tier, info["exp_bonus"], info["gold_bonus"], info["text"]) return { "ok": true, "tier": picked_tier, "tier_name": info["name"], "text": info["text"], "exp_bonus": info["exp_bonus"], "gold_bonus": info["gold_bonus"], "remaining_gold": remaining_gold, "can_buy_talisman": can_buy_talisman, "talisman_cost": TALISMAN_COST_GOLD } # 购买转运符咒化解凶煞 func buy_karma_talisman(inventory: Array, player_gold: int) -> Dictionary: if player_gold < TALISMAN_COST_GOLD: system_error.emit("NOT_ENOUGH_GOLD_TALISMAN") return {"ok": false, "reason": "NOT_ENOUGH_GOLD_TALISMAN", "msg": "购买辟邪转运符金币不足!需要 15,000 金币。"} var remaining_gold = player_gold - TALISMAN_COST_GOLD _give_or_stack_item(inventory, VNUM_TALISMAN, 1) has_karma_talisman = true # 若当前为大凶,直接洗去负面金币减成,转化为中吉 if current_fortune_tier == TIER_BAD_LUCK or current_fortune_tier == TIER_NORMAL: current_fortune_tier = TIER_GOOD_LUCK talisman_acquired.emit(VNUM_TALISMAN, TALISMAN_COST_GOLD) return { "ok": true, "talisman_vnum": VNUM_TALISMAN, "new_tier": current_fortune_tier, "remaining_gold": remaining_gold, "msg": "你佩戴上了老妪开光的辟邪转运符,周身晦暗之气顿时消散,运势转危为安!" } # ========================================== # 2. 乐透刮刮乐奖券开奖 (xxx_change_to_lotto.quest) # ========================================== func play_scratch_lottery( inventory: Array, ticket_slot: int, forced_digits: Array = [] ) -> Dictionary: if ticket_slot < 0 or ticket_slot >= inventory.size() or inventory[ticket_slot] == null: system_error.emit("INVALID_TICKET_SLOT") return {"ok": false, "reason": "INVALID_TICKET_SLOT", "msg": "未找到指定的乐透彩票!"} var item = inventory[ticket_slot] var vnum = int(item.get("vnum", 0)) if vnum != VNUM_LOTTO_TICKET and vnum != VNUM_LOTTO_BOOK: system_error.emit("NOT_A_LOTTO_TICKET") return {"ok": false, "reason": "NOT_A_LOTTO_TICKET", "msg": "该物品并不是乐透彩票奖券!"} # 消耗 1 张彩票 _consume_item_one(inventory, ticket_slot) # 刮开 3 位幸运数字 (1~9) var digits: Array = [] if forced_digits.size() == 3: digits = forced_digits.duplicate() else: digits = [randi_range(1, 9), randi_range(1, 9), randi_range(1, 9)] var d1 = int(digits[0]) var d2 = int(digits[1]) var d3 = int(digits[2]) var prize_tier := "" var prize_gold := 0 var prize_item := 0 var prize_name := "" if d1 == d2 and d2 == d3: # 三星连珠:特等大奖 (1,000,000 金币 + 祝福卷轴 25040) prize_tier = "grand_jackpot" prize_gold = 1000000 prize_item = VNUM_BLESSING_SCROLL prize_name = "【特等头奖】1,000,000 金币 + 祝福卷轴" _give_or_stack_item(inventory, prize_item, 1) elif d1 == d2 or d2 == d3 or d1 == d3: # 双星闪耀:二等奖 (200,000 金币) prize_tier = "second_prize" prize_gold = 200000 prize_item = 0 prize_name = "【二等奖】200,000 金币" else: # 安慰奖:30,000 金币 prize_tier = "consolation_prize" prize_gold = 30000 prize_item = 0 prize_name = "【幸运鼓励奖】30,000 金币" var record = { "digits": digits, "prize_tier": prize_tier, "prize_gold": prize_gold, "prize_item": prize_item } lotto_history.append(record) lottery_scratched.emit(digits, prize_tier, prize_gold, prize_item) return { "ok": true, "digits": digits, "prize_tier": prize_tier, "prize_name": prize_name, "prize_gold": prize_gold, "prize_item": prize_item, "msg": "刮开刮刮乐涂层,赫然显现数字 [%d, %d, %d]!恭喜斩获 %s!" % [d1, d2, d3, prize_name] } # 获取当前占卜增益属性 func get_active_fortune_bonus() -> Dictionary: if current_fortune_tier == "" or fortune_time_remaining <= 0.0: return {"exp_bonus": 0.0, "gold_bonus": 0.0, "tier": ""} var info = FORTUNE_DESCRIPTIONS[current_fortune_tier] return { "tier": current_fortune_tier, "exp_bonus": info["exp_bonus"], "gold_bonus": info["gold_bonus"], "time_remaining": fortune_time_remaining } # 时间推移 func update(delta: float) -> void: if fortune_time_remaining > 0.0: fortune_time_remaining -= delta if fortune_time_remaining <= 0.0: fortune_time_remaining = 0.0 current_fortune_tier = "" # 序列化与反序列化 func serialize() -> Dictionary: return { "current_fortune_tier": current_fortune_tier, "fortune_time_remaining": fortune_time_remaining, "has_karma_talisman": has_karma_talisman, "lotto_history": lotto_history.duplicate(true) } func deserialize(data: Dictionary) -> void: current_fortune_tier = str(data.get("current_fortune_tier", "")) fortune_time_remaining = float(data.get("fortune_time_remaining", 0.0)) has_karma_talisman = bool(data.get("has_karma_talisman", false)) lotto_history = data.get("lotto_history", []).duplicate(true) # 内部消耗与堆叠工具 func _consume_item_one(inventory: Array, slot: int) -> void: if slot < 0 or slot >= inventory.size() or inventory[slot] == null: return var item = inventory[slot] var count = int(item.get("count", 1)) if count > 1: item["count"] = count - 1 else: inventory[slot] = null func _give_or_stack_item(inventory: Array, vnum: int, count: int) -> void: for it in inventory: if it != null and int(it.get("vnum", 0)) == vnum: it["count"] = int(it.get("count", 1)) + count return for i in range(inventory.size()): if inventory[i] == null: inventory[i] = {"vnum": vnum, "count": count} return inventory.append({"vnum": vnum, "count": count})