Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Saving Princess: implement new game #3238

Merged
merged 53 commits into from
Dec 7, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
882ff7c
Saving Princess: initial commit
LeonarthCG Apr 30, 2024
28d6764
settings -> options
LeonarthCG Apr 30, 2024
b522aee
settings -> options
LeonarthCG Apr 30, 2024
61b9450
replace RegionData class with List[str]
LeonarthCG Apr 30, 2024
447fe96
world: MultiWorld -> multiworld: MultiWorld
LeonarthCG Apr 30, 2024
fb198f5
use world's random instead of multiworld's
LeonarthCG Apr 30, 2024
d75e2f5
use state's has_any and has_all where applicable
LeonarthCG Apr 30, 2024
53970e6
Merge remote-tracking branch 'upstream/main'
LeonarthCG May 1, 2024
6e962a0
remove unused StartInventory import
LeonarthCG May 1, 2024
9fed35c
reorder PerGameCommonOptions
LeonarthCG May 1, 2024
76d2df5
Merge remote-tracking branch 'upstream/main'
LeonarthCG May 2, 2024
9d1e070
fix relative AutoWorld import
LeonarthCG May 2, 2024
80d17b1
clean up double spaces
LeonarthCG May 2, 2024
13b2da9
local commands -> Local Commands
LeonarthCG May 2, 2024
aef8a94
Merge branch 'main' of https://github.com/LeonarthCG/Archipelago
LeonarthCG May 2, 2024
e2005ac
remove redundant which items section
LeonarthCG May 2, 2024
572c87c
game info rework
LeonarthCG May 2, 2024
1806c41
clean up item count redundancy
LeonarthCG May 2, 2024
304a3fe
Merge remote-tracking branch 'upstream/main'
LeonarthCG May 2, 2024
8e357b6
Merge remote-tracking branch 'upstream/main'
LeonarthCG May 2, 2024
4fd6a77
add game to readme and codeowners
LeonarthCG May 2, 2024
fbd675f
fix get_region_entrance return type
LeonarthCG May 2, 2024
5e8abf1
world.multiworld.get -> world.get
LeonarthCG May 2, 2024
46e0e33
add more events
LeonarthCG May 2, 2024
eb8ac29
Merge remote-tracking branch 'upstream/main'
LeonarthCG May 5, 2024
b8dc8b7
add client/autoupdater to launcher
LeonarthCG May 5, 2024
6f7d45f
reorder commands in game info
LeonarthCG May 5, 2024
301246b
update docs with automated installation info
LeonarthCG May 5, 2024
37dca62
add quick links to doc
LeonarthCG May 5, 2024
4b8d3c5
Update setup_en.md
LeonarthCG May 5, 2024
7cd9815
Merge remote-tracking branch 'upstream/main'
LeonarthCG Sep 26, 2024
a213c4a
remove standalone saving princess client
LeonarthCG Sep 29, 2024
3b76c40
doc fixes
LeonarthCG Sep 29, 2024
a7983a4
code improvements and redundant default removal
LeonarthCG Sep 29, 2024
3fd22eb
add option to change launch coammnd
LeonarthCG Sep 29, 2024
4f30fb8
simplify valid install check
LeonarthCG Sep 29, 2024
8ef33f2
mod installer improvements
LeonarthCG Sep 29, 2024
25b929f
Merge remote-tracking branch 'upstream/main'
LeonarthCG Sep 29, 2024
4e4b577
add option groups and presets
LeonarthCG Sep 29, 2024
cf40ec0
add required client version
LeonarthCG Sep 29, 2024
e5fb5b2
update docs about cheat items pop-ups
LeonarthCG Sep 29, 2024
0b8854c
add Steam Input issue to faq
LeonarthCG Sep 29, 2024
4397e73
Merge branch 'main' into main
NewSoupVi Nov 29, 2024
399c65c
Saving Princess: BRAINOS requires all weapons
LeonarthCG Nov 30, 2024
a9bc647
Saving Princess: Download dll and patch together
LeonarthCG Nov 30, 2024
86e4c23
Saving Princess: Add URI launch support
LeonarthCG Nov 30, 2024
eed470f
Saving Princess: goal also requires all weapons
LeonarthCG Dec 1, 2024
b9cb509
Saving Princess: update docs
LeonarthCG Dec 1, 2024
b0b3a2d
Saving Princess: extend([item]) -> append(item)
LeonarthCG Dec 1, 2024
a749002
Saving Princess: automatic connection validation
LeonarthCG Dec 1, 2024
efd5316
Merge branch 'main' into main
LeonarthCG Dec 1, 2024
06edf6b
Saving Princess: change subprocess .run to .Popen
LeonarthCG Dec 7, 2024
3119a3f
Merge branch 'main' into main
LeonarthCG Dec 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions worlds/saving_princess/Constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
GAME_NAME: str = "Saving Princess"
BASE_ID: int = 0x53565052494E # SVPRIN

# item names
ITEM_WEAPON_CHARGE: str = "Powered Blaster"
ITEM_WEAPON_FIRE: str = "Flamethrower"
ITEM_WEAPON_ICE: str = "Ice Spreadshot"
ITEM_WEAPON_VOLT: str = "Volt Laser"
ITEM_MAX_HEALTH: str = "Life Extension"
ITEM_MAX_AMMO: str = "Clip Extension"
ITEM_RELOAD_SPEED: str = "Faster Reload"
ITEM_SPECIAL_AMMO: str = "Special Extension"
ITEM_JACKET: str = "Jacket"

EP_ITEM_GUARD_GONE: str = "Cave Key"
EP_ITEM_CLIFF_GONE: str = "Volcanic Key"
EP_ITEM_ACE_GONE: str = "Arctic Key"
EP_ITEM_SNAKE_GONE: str = "Swamp Key"
EP_ITEM_POWER_ON: str = "System Power"

FILLER_ITEM_HEAL: str = "Full Heal"
FILLER_ITEM_QUICK_FIRE: str = "Quick-fire Mode"
FILLER_ITEM_ACTIVE_CAMO: str = "Active Camouflage"

TRAP_ITEM_ICE: str = "Ice Trap"
TRAP_ITEM_SHAKES: str = "Shake Trap"
TRAP_ITEM_NINJA: str = "Ninja Trap"

EVENT_ITEM_VICTORY: str = "PRINCESS"

# location names, EP stands for Expanded Pool
LOCATION_CAVE_AMMO: str = "Cave: After Wallboss"
LOCATION_CAVE_RELOAD: str = "Cave: Balcony"
LOCATION_CAVE_HEALTH: str = "Cave: Spike pit"
LOCATION_CAVE_WEAPON: str = "Cave: Powered Blaster chest"
LOCATION_VOLCANIC_RELOAD: str = "Volcanic: Hot coals"
LOCATION_VOLCANIC_HEALTH: str = "Volcanic: Under bridge"
LOCATION_VOLCANIC_AMMO: str = "Volcanic: Behind wall"
LOCATION_VOLCANIC_WEAPON: str = "Volcanic: Flamethrower chest"
LOCATION_ARCTIC_AMMO: str = "Arctic: Before pipes"
LOCATION_ARCTIC_RELOAD: str = "Arctic: After Guard"
LOCATION_ARCTIC_HEALTH: str = "Arctic: Under snow"
LOCATION_ARCTIC_WEAPON: str = "Arctic: Ice Spreadshot chest"
LOCATION_JACKET: str = "Arctic: Jacket chest"
LOCATION_HUB_AMMO: str = "Hub: Hidden near Arctic"
LOCATION_HUB_HEALTH: str = "Hub: Hidden near Cave"
LOCATION_HUB_RELOAD: str = "Hub: Hidden near Swamp"
LOCATION_SWAMP_AMMO: str = "Swamp: Bramble room"
LOCATION_SWAMP_HEALTH: str = "Swamp: Down the chimney"
LOCATION_SWAMP_RELOAD: str = "Swamp: Wall maze"
LOCATION_SWAMP_SPECIAL: str = "Swamp: Special Extension chest"
LOCATION_ELECTRICAL_RELOAD: str = "Electrical: Near generator"
LOCATION_ELECTRICAL_HEALTH: str = "Electrical: Behind wall"
LOCATION_ELECTRICAL_AMMO: str = "Electrical: Before Malakhov"
LOCATION_ELECTRICAL_WEAPON: str = "Electrical: Volt Laser chest"

EP_LOCATION_CAVE_MINIBOSS: str = "Cave: Wallboss (Boss)"
EP_LOCATION_CAVE_BOSS: str = "Cave: Guard (Boss)"
EP_LOCATION_VOLCANIC_BOSS: str = "Volcanic: Cliff (Boss)"
EP_LOCATION_ARCTIC_BOSS: str = "Arctic: Ace (Boss)"
EP_LOCATION_HUB_CONSOLE: str = "Hub: Console login"
EP_LOCATION_HUB_NINJA_SCARE: str = "Hub: Ninja scare (Boss?)"
EP_LOCATION_SWAMP_BOSS: str = "Swamp: Snake (Boss)"
EP_LOCATION_ELEVATOR_NINJA_FIGHT: str = "Elevator: Ninja (Boss)"
EP_LOCATION_ELECTRICAL_EXTRA: str = "Electrical: Tesla orb"
EP_LOCATION_ELECTRICAL_MINIBOSS: str = "Electrical: Generator (Boss)"
EP_LOCATION_ELECTRICAL_BOSS: str = "Electrical: Malakhov (Boss)"
EP_LOCATION_ELECTRICAL_FINAL_BOSS: str = "Electrical: BRAINOS (Boss)"

EVENT_LOCATION_VICTORY: str = "Mission Objective"

# region names
REGION_MENU: str = "Menu"
REGION_CAVE: str = "Cave"
REGION_VOLCANIC: str = "Volcanic"
REGION_ARCTIC: str = "Arctic"
REGION_HUB: str = "Hub"
REGION_SWAMP: str = "Swamp"
REGION_ELECTRICAL: str = "Electrical"
REGION_ELECTRICAL_POWERED: str = "Electrical (Power On)"
92 changes: 92 additions & 0 deletions worlds/saving_princess/Items.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
from typing import Optional, Dict

from BaseClasses import Item, ItemClassification as ItemClass

from .Constants import *


class SavingPrincessItem(Item):
game: str = GAME_NAME


class ItemData:
item_class: ItemClass
code: Optional[int]
count: int # Number of copies for the item that will be made of class item_class
count_extra: int # Number of extra copies for the item that will be made as useful

def __init__(self, item_class: ItemClass, code: Optional[int] = None, count: int = 1, count_extra: int = 0):
self.item_class = item_class

self.code = code
if code is not None:
self.code += BASE_ID

if self.item_class == ItemClass.filler or code is None:
LeonarthCG marked this conversation as resolved.
Show resolved Hide resolved
self.count = 0
self.count_extra = 0
else:
self.count = count
self.count_extra = count_extra

def create_item(self, player: int):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of creating these every single time you create an item, why not just make lookup tables directly beforehand that are shared and then use those?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I'm not sure I follow.
I think you mean count and count_extra? But I'm pretty sure those are only touched once per entry in my item dictionary, which is the only place ItemData instances are made (unless I made a mistake somewhere).

Or maybe it's my class naming that is confusing? ItemData holds data used to create SavingPrincessItem instances, but the SavingPrincessItem class doesn't have ItemData in it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm saying that every time you call create_item the index and name variables get totally rebuilt. But since the item dict is static, you could just create these once, right after making the item dict itself instead of creating them many times

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand now. Maybe something like this would make more sense:

    def create_item(self, player: int):
        return SavingPrincessItem(item_data_names[self], self.item_class, self.code, player)

[... all of the ItemData dict stuff ...]

item_data_names: Dict[ItemData, str] = {value: key for key, value in item_dict.items()}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me

index = list(item_dict.values()).index(self)
name = list(item_dict.keys())[index]
return SavingPrincessItem(name, self.item_class, self.code, player)


item_dict_weapons: Dict[str, ItemData] = {
ITEM_WEAPON_CHARGE: ItemData(ItemClass.progression, 0),
ITEM_WEAPON_FIRE: ItemData(ItemClass.progression, 1),
ITEM_WEAPON_ICE: ItemData(ItemClass.progression, 2),
ITEM_WEAPON_VOLT: ItemData(ItemClass.progression, 3),
}

item_dict_upgrades: Dict[str, ItemData] = {
ITEM_MAX_HEALTH: ItemData(ItemClass.progression, 4, 2, 4),
ITEM_MAX_AMMO: ItemData(ItemClass.progression, 5, 2, 4),
ITEM_RELOAD_SPEED: ItemData(ItemClass.progression, 6, 4, 2),
ITEM_SPECIAL_AMMO: ItemData(ItemClass.useful, 7),
}

item_dict_base: Dict[str, ItemData] = {
**item_dict_weapons,
**item_dict_upgrades,
ITEM_JACKET: ItemData(ItemClass.useful, 8),
}

item_dict_keys: Dict[str, ItemData] = {
EP_ITEM_GUARD_GONE: ItemData(ItemClass.progression, 9),
EP_ITEM_CLIFF_GONE: ItemData(ItemClass.progression, 10),
EP_ITEM_ACE_GONE: ItemData(ItemClass.progression, 11),
EP_ITEM_SNAKE_GONE: ItemData(ItemClass.progression, 12),
}

item_dict_expanded: Dict[str, ItemData] = {
**item_dict_base,
**item_dict_keys,
EP_ITEM_POWER_ON: ItemData(ItemClass.progression, 13),
}

item_dict_filler: Dict[str, ItemData] = {
FILLER_ITEM_HEAL: ItemData(ItemClass.filler, 14, 0),
FILLER_ITEM_QUICK_FIRE: ItemData(ItemClass.filler, 15, 0),
FILLER_ITEM_ACTIVE_CAMO: ItemData(ItemClass.filler, 16, 0),
}

item_dict_traps: Dict[str, ItemData] = {
TRAP_ITEM_ICE: ItemData(ItemClass.trap, 17, 0),
TRAP_ITEM_SHAKES: ItemData(ItemClass.trap, 18, 0),
TRAP_ITEM_NINJA: ItemData(ItemClass.trap, 19, 0),
}

item_dict_events: Dict[str, ItemData] = {
EVENT_ITEM_VICTORY: ItemData(ItemClass.progression, None, 0)
}

item_dict: Dict[str, ItemData] = {
**item_dict_expanded,
**item_dict_filler,
**item_dict_traps,
**item_dict_events,
}
72 changes: 72 additions & 0 deletions worlds/saving_princess/Locations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
from typing import Optional, Dict

from BaseClasses import Location

from .Constants import *


class SavingPrincessLocation(Location):
game: str = GAME_NAME


class LocData:
code: Optional[int]

def __init__(self, code: Optional[int] = None):
if code is not None:
self.code = code + BASE_ID
else:
self.code = None


location_dict_base: Dict[str, LocData] = {
LOCATION_CAVE_AMMO: LocData(0),
LOCATION_CAVE_RELOAD: LocData(1),
LOCATION_CAVE_HEALTH: LocData(2),
LOCATION_CAVE_WEAPON: LocData(3),
LOCATION_VOLCANIC_RELOAD: LocData(4),
LOCATION_VOLCANIC_HEALTH: LocData(5),
LOCATION_VOLCANIC_AMMO: LocData(6),
LOCATION_VOLCANIC_WEAPON: LocData(7),
LOCATION_ARCTIC_AMMO: LocData(8),
LOCATION_ARCTIC_RELOAD: LocData(9),
LOCATION_ARCTIC_HEALTH: LocData(10),
LOCATION_ARCTIC_WEAPON: LocData(11),
LOCATION_JACKET: LocData(12),
LOCATION_HUB_AMMO: LocData(13),
LOCATION_HUB_HEALTH: LocData(14),
LOCATION_HUB_RELOAD: LocData(15),
LOCATION_SWAMP_AMMO: LocData(16),
LOCATION_SWAMP_HEALTH: LocData(17),
LOCATION_SWAMP_RELOAD: LocData(18),
LOCATION_SWAMP_SPECIAL: LocData(19),
LOCATION_ELECTRICAL_RELOAD: LocData(20),
LOCATION_ELECTRICAL_HEALTH: LocData(21),
LOCATION_ELECTRICAL_AMMO: LocData(22),
LOCATION_ELECTRICAL_WEAPON: LocData(23),
}

location_dict_expanded: Dict[str, LocData] = {
**location_dict_base,
EP_LOCATION_CAVE_MINIBOSS: LocData(24),
EP_LOCATION_CAVE_BOSS: LocData(25),
EP_LOCATION_VOLCANIC_BOSS: LocData(26),
EP_LOCATION_ARCTIC_BOSS: LocData(27),
EP_LOCATION_HUB_CONSOLE: LocData(28),
EP_LOCATION_HUB_NINJA_SCARE: LocData(29),
EP_LOCATION_SWAMP_BOSS: LocData(30),
EP_LOCATION_ELEVATOR_NINJA_FIGHT: LocData(31),
EP_LOCATION_ELECTRICAL_EXTRA: LocData(32),
EP_LOCATION_ELECTRICAL_MINIBOSS: LocData(33),
EP_LOCATION_ELECTRICAL_BOSS: LocData(34),
EP_LOCATION_ELECTRICAL_FINAL_BOSS: LocData(35),
}

location_dict_event: Dict[str, LocData] = {
EVENT_LOCATION_VICTORY: LocData(None),
}

location_dict: Dict[str, LocData] = {
**location_dict_expanded,
**location_dict_event,
}
119 changes: 119 additions & 0 deletions worlds/saving_princess/Options.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
from dataclasses import dataclass

from Options import PerGameCommonOptions, DeathLink, StartInventoryPool, Choice, DefaultOnToggle, Range, Toggle


class ExpandedPool(DefaultOnToggle):
"""
Determines if places other than chests and special weapons will be locations.
This includes boss fights as well as powering the tesla orb and completing the console login.
In Expanded Pool, system power is instead restored when receiving the System Power item.
Similarly, the final area door will open once the four Key items, one for each main area, have been found.
"""
display_name = "Expanded Item Pool"


class InstantSaving(DefaultOnToggle):
"""
When enabled, save points activate with no delay when touched.
This makes saving much faster, at the cost of being unable to pick and choose when to save in order to save warp.
"""
display_name = "Instant Saving"


class SprintAvailability(Choice):
"""
Determines under which conditions the debug sprint is made accessible to the player.
To sprint, hold down Ctrl if playing on keyboard, or Left Bumper if on gamepad (remappable).
With Jacket: you will not be able to sprint until after the Jacket item has been found.
"""
display_name = "Sprint Availability"
option_never_available = 0
option_always_available = 1
option_available_with_jacket = 2
default = option_available_with_jacket


class CliffWeaponUpgrade(Choice):
"""
Determines which weapon Cliff uses against you, base or upgraded.
This does not change the available strategies all that much.
Vanilla: Cliff adds fire to his grenades if Ace has been defeated.
If playing with the expanded pool, the Arctic Key will trigger the change instead.
"""
display_name = "Cliff Weapon Upgrade"
option_never_upgraded = 0
option_always_upgraded = 1
option_vanilla = 2
default = option_always_upgraded


class AceWeaponUpgrade(Choice):
"""
Determines which weapon Ace uses against you, base or upgraded.
Ace with his base weapon is very hard to dodge, the upgraded weapon offers a more balanced experience.
Vanilla: Ace uses ice attacks if Cliff has been defeated.
If playing with the expanded pool, the Volcanic Key will trigger the change instead.
"""
display_name = "Ace Weapon Upgrade"
option_never_upgraded = 0
option_always_upgraded = 1
option_vanilla = 2
default = option_always_upgraded


class ScreenShakeIntensity(Range):
"""
Percentage multiplier for screen shake effects.
0% means the screen will not shake at all.
100% means the screen shake will be the same as in vanilla.
"""
display_name = "Screen Shake Intensity %"
range_start = 0
range_end = 100
default = 50
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems a little weird that the default isn't vanilla?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, having played the game now, this no longer seems weird XD



class IFramesDuration(Range):
"""
Percentage multiplier for Portia's invincibility frames.
0% means you will have no invincibility frames.
100% means invincibility frames will be the same as vanilla.
"""
display_name = "IFrame Duration %"
range_start = 0
range_end = 400
default = 100


class TrapChance(Range):
"""
Likelihood of a filler item becoming a trap.
"""
display_name = "Trap Chance"
range_start = 0
range_end = 100
default = 50


class MusicShuffle(Toggle):
"""
Enables music shuffling.
The title screen song is not shuffled, as it plays before the client connects.
"""
display_name = "Music Shuffle"


@dataclass
class SavingPrincessOptions(PerGameCommonOptions):
death_link: DeathLink
start_inventory_from_pool: StartInventoryPool
expanded_pool: ExpandedPool
instant_saving: InstantSaving
sprint_availability: SprintAvailability
cliff_weapon_upgrade: CliffWeaponUpgrade
ace_weapon_upgrade: AceWeaponUpgrade
shake_intensity: ScreenShakeIntensity
iframes_duration: IFramesDuration
trap_chance: TrapChance
music_shuffle: MusicShuffle
LeonarthCG marked this conversation as resolved.
Show resolved Hide resolved
Loading