KDL3: update to gifting protocol 3 and update settings usage (#4814)

* gift version 3

* update settings usage

* that really has just been broken this entire time

* remove unnecessary print

* Update client.py

* fix random flavor handling

* fix incorrect sender/receiver

---------

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
This commit is contained in:
Silvris
2025-06-16 12:00:47 -05:00
committed by GitHub
parent 6f244c4661
commit 211456242e
4 changed files with 168 additions and 168 deletions

View File

@@ -90,7 +90,7 @@ def cmd_gift(self: "SNIClientCommandProcessor") -> None:
async_start(update_object(self.ctx, f"Giftboxes;{self.ctx.team}", { async_start(update_object(self.ctx, f"Giftboxes;{self.ctx.team}", {
f"{self.ctx.slot}": f"{self.ctx.slot}":
{ {
"IsOpen": handler.gifting, "is_open": handler.gifting,
**kdl3_gifting_options **kdl3_gifting_options
} }
})) }))
@@ -175,11 +175,11 @@ class KDL3SNIClient(SNIClient):
key, gift = ctx.stored_data[self.giftbox_key].popitem() key, gift = ctx.stored_data[self.giftbox_key].popitem()
await pop_object(ctx, self.giftbox_key, key) await pop_object(ctx, self.giftbox_key, key)
# first, special cases # first, special cases
traits = [trait["Trait"] for trait in gift["Traits"]] traits = [trait["trait"] for trait in gift["traits"]]
if "Candy" in traits or "Invincible" in traits: if "Candy" in traits or "Invincible" in traits:
# apply invincibility candy # apply invincibility candy
self.item_queue.append(0x43) self.item_queue.append(0x43)
elif "Tomato" in traits or "tomato" in gift["ItemName"].lower(): elif "Tomato" in traits or "tomato" in gift["item_name"].lower():
# apply maxim tomato # apply maxim tomato
# only want tomatos here, no other vegetable is that good # only want tomatos here, no other vegetable is that good
self.item_queue.append(0x42) self.item_queue.append(0x42)
@@ -187,7 +187,7 @@ class KDL3SNIClient(SNIClient):
# Apply 1-Up # Apply 1-Up
self.item_queue.append(0x41) self.item_queue.append(0x41)
elif "Currency" in traits or "Star" in traits: elif "Currency" in traits or "Star" in traits:
value = gift["ItemValue"] value = gift.get("item_value", 1)
if value >= 50000: if value >= 50000:
self.item_queue.append(0x46) self.item_queue.append(0x46)
elif value >= 30000: elif value >= 30000:
@@ -210,8 +210,8 @@ class KDL3SNIClient(SNIClient):
# check if it's tasty # check if it's tasty
if any(x in traits for x in ["Consumable", "Food", "Drink", "Heal", "Health"]): if any(x in traits for x in ["Consumable", "Food", "Drink", "Heal", "Health"]):
# it's tasty!, use quality to decide how much to heal # it's tasty!, use quality to decide how much to heal
quality = max((trait["Quality"] for trait in gift["Traits"] quality = max((trait.get("quality", 1.0) for trait in gift["traits"]
if trait["Trait"] in ["Consumable", "Food", "Drink", "Heal", "Health"])) if trait["trait"] in ["Consumable", "Food", "Drink", "Heal", "Health"]))
quality = min(10, quality * 2) quality = min(10, quality * 2)
else: else:
# it's not really edible, but he'll eat it anyway # it's not really edible, but he'll eat it anyway
@@ -236,23 +236,23 @@ class KDL3SNIClient(SNIClient):
for slot, info in ctx.stored_data[self.motherbox_key].items(): for slot, info in ctx.stored_data[self.motherbox_key].items():
if int(slot) == ctx.slot and len(ctx.stored_data[self.motherbox_key]) > 1: if int(slot) == ctx.slot and len(ctx.stored_data[self.motherbox_key]) > 1:
continue continue
desire = len(set(info["DesiredTraits"]).intersection([trait["Trait"] for trait in gift_base["Traits"]])) desire = len(set(info["desired_traits"]).intersection([trait["trait"] for trait in gift_base["traits"]]))
if desire > most_applicable: if desire > most_applicable:
most_applicable = desire most_applicable = desire
most_applicable_slot = int(slot) most_applicable_slot = int(slot)
elif most_applicable_slot != ctx.slot and most_applicable == -1 and info["AcceptsAnyGift"]: elif most_applicable_slot == ctx.slot and most_applicable == -1 and info["accepts_any_gift"]:
# only send to ourselves if no one else will take it # only send to ourselves if no one else will take it
most_applicable_slot = int(slot) most_applicable_slot = int(slot)
# print(most_applicable, most_applicable_slot) # print(most_applicable, most_applicable_slot)
item_uuid = uuid.uuid4().hex item_uuid = uuid.uuid4().hex
item = { item = {
**gift_base, **gift_base,
"ID": item_uuid, "id": item_uuid,
"Sender": ctx.player_names[ctx.slot], "sender_slot": ctx.slot,
"Receiver": ctx.player_names[most_applicable_slot], "receiver_slot": most_applicable_slot,
"SenderTeam": ctx.team, "sender_team": ctx.team,
"ReceiverTeam": ctx.team, # for the moment "receiver_team": ctx.team, # for the moment
"IsRefund": False "is_refund": False
} }
# print(item) # print(item)
await update_object(ctx, f"Giftbox;{ctx.team};{most_applicable_slot}", { await update_object(ctx, f"Giftbox;{ctx.team};{most_applicable_slot}", {
@@ -276,8 +276,9 @@ class KDL3SNIClient(SNIClient):
if not self.initialize_gifting: if not self.initialize_gifting:
self.giftbox_key = f"Giftbox;{ctx.team};{ctx.slot}" self.giftbox_key = f"Giftbox;{ctx.team};{ctx.slot}"
self.motherbox_key = f"Giftboxes;{ctx.team}" self.motherbox_key = f"Giftboxes;{ctx.team}"
enable_gifting = await snes_read(ctx, KDL3_GIFTING_FLAG, 0x01) enable_gifting = await snes_read(ctx, KDL3_GIFTING_FLAG, 0x02)
await initialize_giftboxes(ctx, self.giftbox_key, self.motherbox_key, bool(enable_gifting[0])) await initialize_giftboxes(ctx, self.giftbox_key, self.motherbox_key,
bool(int.from_bytes(enable_gifting, "little")))
self.initialize_gifting = True self.initialize_gifting = True
# can't check debug anymore, without going and copying the value. might be important later. # can't check debug anymore, without going and copying the value. might be important later.
if not self.levels: if not self.levels:
@@ -350,19 +351,19 @@ class KDL3SNIClient(SNIClient):
self.item_queue.append(item_idx | 0x80) self.item_queue.append(item_idx | 0x80)
# handle gifts here # handle gifts here
gifting_status = await snes_read(ctx, KDL3_GIFTING_FLAG, 0x01) gifting_status = int.from_bytes(await snes_read(ctx, KDL3_GIFTING_FLAG, 0x02), "little")
if hasattr(ctx, "gifting") and ctx.gifting: if hasattr(self, "gifting") and self.gifting:
if gifting_status[0]: if gifting_status:
gift = await snes_read(ctx, KDL3_GIFTING_SEND, 0x01) gift = await snes_read(ctx, KDL3_GIFTING_SEND, 0x01)
if gift[0]: if gift[0]:
# we have a gift to send # we have a gift to send
await self.pick_gift_recipient(ctx, gift[0]) await self.pick_gift_recipient(ctx, gift[0])
snes_buffered_write(ctx, KDL3_GIFTING_SEND, bytes([0x00])) snes_buffered_write(ctx, KDL3_GIFTING_SEND, bytes([0x00]))
else: else:
snes_buffered_write(ctx, KDL3_GIFTING_FLAG, bytes([0x01])) snes_buffered_write(ctx, KDL3_GIFTING_FLAG, bytes([0x01, 0x00]))
else: else:
if gifting_status[0]: if gifting_status:
snes_buffered_write(ctx, KDL3_GIFTING_FLAG, bytes([0x00])) snes_buffered_write(ctx, KDL3_GIFTING_FLAG, bytes([0x00, 0x00]))
await snes_flush_writes(ctx) await snes_flush_writes(ctx)

View File

@@ -37,157 +37,158 @@ async def initialize_giftboxes(ctx: "SNIContext", giftbox_key: str, motherbox_ke
ctx.set_notify(motherbox_key, giftbox_key) ctx.set_notify(motherbox_key, giftbox_key)
await update_object(ctx, f"Giftboxes;{ctx.team}", {f"{ctx.slot}": await update_object(ctx, f"Giftboxes;{ctx.team}", {f"{ctx.slot}":
{ {
"IsOpen": is_open, "is_open": is_open,
**kdl3_gifting_options **kdl3_gifting_options
}}) }})
await update_object(ctx, f"Giftbox;{ctx.team};{ctx.slot}", {})
ctx.client_handler.gifting = is_open ctx.client_handler.gifting = is_open
kdl3_gifting_options = { kdl3_gifting_options = {
"AcceptsAnyGift": True, "accepts_any_gift": True,
"DesiredTraits": [ "desired_traits": [
"Consumable", "Food", "Drink", "Candy", "Tomato", "Consumable", "Food", "Drink", "Candy", "Tomato",
"Invincible", "Life", "Heal", "Health", "Trap", "Invincible", "Life", "Heal", "Health", "Trap",
"Goo", "Gel", "Slow", "Slowness", "Eject", "Removal" "Goo", "Gel", "Slow", "Slowness", "Eject", "Removal"
], ],
"MinimumGiftVersion": 2, "minimum_gift_version": 3,
} }
kdl3_gifts = { kdl3_gifts = {
1: { 1: {
"ItemName": "1-Up", "item_name": "1-Up",
"Amount": 1, "amount": 1,
"ItemValue": 400000, "item_value": 400000,
"Traits": [ "traits": [
{ {
"Trait": "Consumable", "trait": "Consumable",
"Quality": 1, "quality": 1,
"Duration": 1, "duration": 1,
}, },
{ {
"Trait": "Life", "trait": "Life",
"Quality": 1, "quality": 1,
"Duration": 1 "duration": 1
} }
] ]
}, },
2: { 2: {
"ItemName": "Maxim Tomato", "item_name": "Maxim Tomato",
"Amount": 1, "amount": 1,
"ItemValue": 500000, "item_value": 500000,
"Traits": [ "traits": [
{ {
"Trait": "Consumable", "trait": "Consumable",
"Quality": 5, "quality": 5,
"Duration": 1, "duration": 1,
}, },
{ {
"Trait": "Heal", "trait": "Heal",
"Quality": 5, "quality": 5,
"Duration": 1, "duration": 1,
}, },
{ {
"Trait": "Food", "trait": "Food",
"Quality": 5, "quality": 5,
"Duration": 1, "duration": 1,
}, },
{ {
"Trait": "Tomato", "trait": "Tomato",
"Quality": 5, "quality": 5,
"Duration": 1, "duration": 1,
}, },
{ {
"Trait": "Vegetable", "trait": "Vegetable",
"Quality": 5, "quality": 5,
"Duration": 1, "duration": 1,
} }
] ]
}, },
3: { 3: {
"ItemName": "Energy Drink", "item_name": "Energy Drink",
"Amount": 1, "amount": 1,
"ItemValue": 100000, "item_value": 100000,
"Traits": [ "traits": [
{ {
"Trait": "Consumable", "trait": "Consumable",
"Quality": 1, "quality": 1,
"Duration": 1, "duration": 1,
}, },
{ {
"Trait": "Heal", "trait": "Heal",
"Quality": 1, "quality": 1,
"Duration": 1, "duration": 1,
}, },
{ {
"Trait": "Drink", "trait": "Drink",
"Quality": 1, "quality": 1,
"Duration": 1, "duration": 1,
}, },
] ]
}, },
5: { 5: {
"ItemName": "Small Star Piece", "item_name": "Small Star Piece",
"Amount": 1, "amount": 1,
"ItemValue": 10000, "item_value": 10000,
"Traits": [ "traits": [
{ {
"Trait": "Currency", "trait": "Currency",
"Quality": 1, "quality": 1,
"Duration": 1, "duration": 1,
}, },
{ {
"Trait": "Money", "trait": "Money",
"Quality": 1, "quality": 1,
"Duration": 1, "duration": 1,
}, },
{ {
"Trait": "Star", "trait": "Star",
"Quality": 1, "quality": 1,
"Duration": 1 "duration": 1
} }
] ]
}, },
6: { 6: {
"ItemName": "Medium Star Piece", "item_name": "Medium Star Piece",
"Amount": 1, "amount": 1,
"ItemValue": 30000, "item_value": 30000,
"Traits": [ "traits": [
{ {
"Trait": "Currency", "trait": "Currency",
"Quality": 3, "quality": 3,
"Duration": 1, "duration": 1,
}, },
{ {
"Trait": "Money", "trait": "Money",
"Quality": 3, "quality": 3,
"Duration": 1, "duration": 1,
}, },
{ {
"Trait": "Star", "trait": "Star",
"Quality": 3, "quality": 3,
"Duration": 1 "duration": 1
} }
] ]
}, },
7: { 7: {
"ItemName": "Large Star Piece", "item_name": "Large Star Piece",
"Amount": 1, "amount": 1,
"ItemValue": 50000, "item_value": 50000,
"Traits": [ "traits": [
{ {
"Trait": "Currency", "trait": "Currency",
"Quality": 5, "quality": 5,
"Duration": 1, "duration": 1,
}, },
{ {
"Trait": "Money", "trait": "Money",
"Quality": 5, "quality": 5,
"Duration": 1, "duration": 1,
}, },
{ {
"Trait": "Star", "trait": "Star",
"Quality": 5, "quality": 5,
"Duration": 1 "duration": 1
} }
] ]
}, },
@@ -195,90 +196,90 @@ kdl3_gifts = {
kdl3_trap_gifts = { kdl3_trap_gifts = {
0: { 0: {
"ItemName": "Gooey Bag", "item_name": "Gooey Bag",
"Amount": 1, "amount": 1,
"ItemValue": 10000, "item_value": 10000,
"Traits": [ "traits": [
{ {
"Trait": "Trap", "trait": "Trap",
"Quality": 1, "quality": 1,
"Duration": 1, "duration": 1,
}, },
{ {
"Trait": "Goo", "trait": "Goo",
"Quality": 1, "quality": 1,
"Duration": 1, "duration": 1,
}, },
{ {
"Trait": "Gel", "trait": "Gel",
"Quality": 1, "quality": 1,
"Duration": 1 "duration": 1
} }
] ]
}, },
1: { 1: {
"ItemName": "Slowness", "item_name": "Slowness",
"Amount": 1, "amount": 1,
"ItemValue": 10000, "item_value": 10000,
"Traits": [ "traits": [
{ {
"Trait": "Trap", "trait": "Trap",
"Quality": 1, "quality": 1,
"Duration": 1, "duration": 1,
}, },
{ {
"Trait": "Slow", "trait": "Slow",
"Quality": 1, "quality": 1,
"Duration": 1, "duration": 1,
}, },
{ {
"Trait": "Slowness", "trait": "Slowness",
"Quality": 1, "quality": 1,
"Duration": 1 "duration": 1
} }
] ]
}, },
2: { 2: {
"ItemName": "Eject Ability", "item_name": "Eject Ability",
"Amount": 1, "amount": 1,
"ItemValue": 10000, "item_value": 10000,
"Traits": [ "traits": [
{ {
"Trait": "Trap", "trait": "Trap",
"Quality": 1, "quality": 1,
"Duration": 1, "duration": 1,
}, },
{ {
"Trait": "Eject", "trait": "Eject",
"Quality": 1, "quality": 1,
"Duration": 1, "duration": 1,
}, },
{ {
"Trait": "Removal", "trait": "Removal",
"Quality": 1, "quality": 1,
"Duration": 1 "duration": 1
} }
] ]
}, },
3: { 3: {
"ItemName": "Bad Meal", "item_name": "Bad Meal",
"Amount": 1, "amount": 1,
"ItemValue": 10000, "item_value": 10000,
"Traits": [ "traits": [
{ {
"Trait": "Trap", "trait": "Trap",
"Quality": 1, "quality": 1,
"Duration": 1, "duration": 1,
}, },
{ {
"Trait": "Damage", "trait": "Damage",
"Quality": 1, "quality": 1,
"Duration": 1, "duration": 1,
}, },
{ {
"Trait": "Food", "trait": "Food",
"Quality": 1, "quality": 1,
"Duration": 1 "duration": 1
} }
] ]
}, },

View File

@@ -289,7 +289,7 @@ class KirbyFlavorPreset(Choice):
option_lime = 12 option_lime = 12
option_lavender = 13 option_lavender = 13
option_miku = 14 option_miku = 14
option_custom = 15 option_custom = -1
default = 0 default = 0
@classmethod @classmethod
@@ -297,7 +297,7 @@ class KirbyFlavorPreset(Choice):
text = text.lower() text = text.lower()
if text == "random": if text == "random":
choice_list = list(cls.name_lookup) choice_list = list(cls.name_lookup)
choice_list.remove(14) choice_list.remove(-1)
return cls(random.choice(choice_list)) return cls(random.choice(choice_list))
return super().from_text(text) return super().from_text(text)
@@ -347,7 +347,7 @@ class GooeyFlavorPreset(Choice):
option_orange = 11 option_orange = 11
option_lime = 12 option_lime = 12
option_lavender = 13 option_lavender = 13
option_custom = 14 option_custom = -1
default = 0 default = 0
@classmethod @classmethod
@@ -355,7 +355,7 @@ class GooeyFlavorPreset(Choice):
text = text.lower() text = text.lower()
if text == "random": if text == "random":
choice_list = list(cls.name_lookup) choice_list = list(cls.name_lookup)
choice_list.remove(14) choice_list.remove(-1)
return cls(random.choice(choice_list)) return cls(random.choice(choice_list))
return super().from_text(text) return super().from_text(text)

View File

@@ -7,7 +7,6 @@ import hashlib
import os import os
import struct import struct
import settings
from worlds.Files import APProcedurePatch, APTokenMixin, APTokenTypes, APPatchExtension from worlds.Files import APProcedurePatch, APTokenMixin, APTokenTypes, APPatchExtension
from .aesthetics import get_palette_bytes, kirby_target_palettes, get_kirby_palette, gooey_target_palettes, \ from .aesthetics import get_palette_bytes, kirby_target_palettes, get_kirby_palette, gooey_target_palettes, \
get_gooey_palette get_gooey_palette
@@ -475,8 +474,7 @@ def patch_rom(world: "KDL3World", patch: KDL3ProcedurePatch) -> None:
patch.write_token(APTokenTypes.WRITE, 0x3D016, world.options.ow_boss_requirement.value.to_bytes(2, "little")) patch.write_token(APTokenTypes.WRITE, 0x3D016, world.options.ow_boss_requirement.value.to_bytes(2, "little"))
patch.write_token(APTokenTypes.WRITE, 0x3D018, world.options.consumables.value.to_bytes(2, "little")) patch.write_token(APTokenTypes.WRITE, 0x3D018, world.options.consumables.value.to_bytes(2, "little"))
patch.write_token(APTokenTypes.WRITE, 0x3D01A, world.options.starsanity.value.to_bytes(2, "little")) patch.write_token(APTokenTypes.WRITE, 0x3D01A, world.options.starsanity.value.to_bytes(2, "little"))
patch.write_token(APTokenTypes.WRITE, 0x3D01C, world.options.gifting.value.to_bytes(2, "little") patch.write_token(APTokenTypes.WRITE, 0x3D01C, world.options.gifting.value.to_bytes(2, "little"))
if world.multiworld.players > 1 else bytes([0, 0]))
patch.write_token(APTokenTypes.WRITE, 0x3D01E, world.options.strict_bosses.value.to_bytes(2, "little")) patch.write_token(APTokenTypes.WRITE, 0x3D01E, world.options.strict_bosses.value.to_bytes(2, "little"))
# don't write gifting for solo game, since there's no one to send anything to # don't write gifting for solo game, since there's no one to send anything to
@@ -594,9 +592,9 @@ def get_base_rom_bytes() -> bytes:
def get_base_rom_path(file_name: str = "") -> str: def get_base_rom_path(file_name: str = "") -> str:
options: settings.Settings = settings.get_settings() from . import KDL3World
if not file_name: if not file_name:
file_name = options["kdl3_options"]["rom_file"] file_name = KDL3World.settings.rom_file
if not os.path.exists(file_name): if not os.path.exists(file_name):
file_name = Utils.user_path(file_name) file_name = Utils.user_path(file_name)
return file_name return file_name