Merge remote-tracking branch 'upstream/master'

This commit is contained in:
ShinyTillDawn 2025-03-28 13:42:04 -04:00
commit 9b59c3128b
41 changed files with 3039 additions and 457 deletions

View File

@ -0,0 +1,28 @@
#nullable enable
using System;
using System.Collections.Generic;
namespace HavenSoft.HexManiac.Core;
/// <summary>
/// If used as AutoDictionary or IDictionary,
/// requesting a value from an unused key will create and add a default value.
/// Casting to a Dictionary will act like a Dictionary.
/// Contains and TryGet still work the normal way.
/// </summary>
public class AutoDictionary<TKey, TValue> : Dictionary<TKey, TValue>, IDictionary<TKey, TValue> where TKey : notnull {
private readonly Func<TKey, TValue> factory;
/// <param name="factory">A method for creating a new value based on a key.</param>
public AutoDictionary(Func<TKey, TValue> factory) => this.factory = factory;
TValue IDictionary<TKey, TValue>.this[TKey key] {
get => this[key];
set => this[key] = value;
}
public new TValue this[TKey key] {
get => TryGetValue(key, out var value) ? value : base[key] = factory(key);
set => base[key] = value;
}
}

View File

@ -0,0 +1,23 @@
using System;
namespace HavenSoft.HexManiac.Core;
public enum DelayWorkResult { WorkScheduled, WorkScheduledAndPreviousWorkCleared }
public interface IDelayWorkTimer {
/// <summary>
/// Returns true if 'DelayCall' has been called, but the work has not yet been executed.
/// </summary>
bool HasScheduledWork { get; }
/// <summary>
/// Will call the action after the delay.
/// Returns 'WorkScheduledAndPreviousWorkCleared' if this delay work timer was already tracking work.
/// </summary>
DelayWorkResult DelayCall(TimeSpan delay, Action action);
/// <summary>
/// Clears any currently scheduled work.
/// </summary>
void Reset();
}

View File

@ -1,105 +1,114 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<ImplicitUsings>false</ImplicitUsings>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<Nullable>annotations</Nullable>
<CodeGenDir>$(SolutionDir)artifacts\$(AssemblyName)\codegen\</CodeGenDir>
<AutoImplement>$(SolutionDir)artifacts\AutoImplement.Tool\bin\$(Configuration)\$(TargetFramework)\AutoImplement.Tool</AutoImplement>
<Platforms>AnyCPU</Platforms>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(SolutionDir)src\SharedAssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="Models\Code\default.axve.axpe.bpee.toml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\default.axve.axpe.toml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\default.axve0.axpe0.toml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\default.axve1.axpe1.toml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\default.bpee.toml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\default.bpge0.toml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\default.bpge1.toml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\default.bpre.bpge.toml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\default.bpre0.bpge0.toml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\default.bpre0.toml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\default.bpre1.bpge1.toml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\default.bpre1.toml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\default.bpei0.toml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\default.toml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\expand_levelup_moves_code.hma">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\default.bprf0.toml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\default.bpef.toml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\armReference.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\animationScriptReference.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\battleScriptReference.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\battleAIScriptReference.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\constantReference.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\docReference.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\pcsReference.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\scriptReference.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\tableReference.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\hma.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Crc32.NET" Version="1.2.0" />
<PackageReference Include="DynamicLanguageRuntime" Version="1.3.3" />
<PackageReference Include="IronPython" Version="3.4.0" />
<PackageReference Include="IronPython.StdLib" Version="3.4.0" />
</ItemGroup>
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<ImplicitUsings>false</ImplicitUsings>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateTargetFrameworkAttribute>false</GenerateTargetFrameworkAttribute>
<Nullable>annotations</Nullable>
<CodeGenDir>$(SolutionDir)artifacts\$(AssemblyName)\codegen\</CodeGenDir>
<AutoImplement>$(SolutionDir)artifacts\AutoImplement.Tool\bin\$(Configuration)\$(TargetFramework)\AutoImplement.Tool</AutoImplement>
<Platforms>AnyCPU</Platforms>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(SolutionDir)src\SharedAssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="Models\Code\default.axve.axpe.bpee.toml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\default.axve.axpe.toml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\default.axve0.axpe0.toml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\default.axve1.axpe1.toml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\default.bpee.toml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\default.bpge0.toml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\default.bpge1.toml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\default.bpre.bpge.toml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\default.bpre0.bpge0.toml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\default.bpre0.toml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\default.bpre1.bpge1.toml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\default.bpre1.toml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\default.bpri.toml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\default.bpei0.toml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\default.toml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\expand_levelup_moves_code.hma">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\default.bprf0.toml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\default.bpef.toml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\armReference.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\animationScriptReference.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\battleScriptReference.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\battleAIScriptReference.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\constantReference.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\constantReference.it.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\docReference.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\pcsReference.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\scriptReference.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\tableReference.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\tableReference.it.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Models\Code\hma.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Crc32.NET" Version="1.2.0" />
<PackageReference Include="DynamicLanguageRuntime" Version="1.3.3" />
<PackageReference Include="IronPython" Version="3.4.0" />
<PackageReference Include="IronPython.StdLib" Version="3.4.0" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,178 @@
- Shiny -------------------------------------------------------------------------------------------------------------------------
# -1: CreateBoxMon,GetMonSpritePalFromSpeciesAndPersonality,GetMonSpritePalStructFromOtIdPersonality,IsShinyOtIdPersonality,sub_XXXXXX0
# 0: apparently used for pokedex pokemon palette choosing, although I can't find the function in pokefirered
BPRI0.scripts.shiny.odds 104C78 # The odds of a pokemon being shiny are n/65535. For example, if you set the odds to 250, then the final odds are about 1 in 262.
BPRI0.scripts.shiny.odds-1 03DA4A,04400C,044082,04439C,0F19FE # The odds of a pokemon being shiny are n/65535. For example, if you set the odds to 250, then the final odds are about 1 in 262.
# additional shiny constants found by AGSMG?, used in credits
BPRI0.scripts.shiny.odds 0F4462,0F44A0,0F44E0,0F4528
# Credit to Rubiibank for finding this!
# 0: NewGameBirchSpeech_CreateLotadSprite,CreateMonSpriteFromNationalDexNumber,CreatePokemonFrontSprite
- Regional Pokedex Length -------------------------------------------------------------------------------------------------------
# 0: SpeciesToPokedexNum,CreatePokedexList,CreatePokedexList,GetPokedexRatingText
# -1: GetHoennPokedexCount,GetPokedexRatingText
# -2: GetPokedexRatingText
# -3: HasAllHoennMons,GetPokedexRatingText
# 0: sub_XXXXXX8,MonCanEvolve
# -1: GetKantoPokedexCount,sub_XXXXXX8,sub_XXXXXXC
# -2: HasAllKantoMons
BPRI0.scripts.pokedex.regional.length 103780,126CDA
BPRI0.scripts.pokedex.regional.length-1 088FE8,10384A,104E9E
BPRI0.scripts.pokedex.regional.length-2 089046
- Townmap Button Positions ------------------------------------------------------------------------------------------------------
# PlaySEForSelectedMapsec,Task_RegionMap,HandleRegionMapInput,SnapToIconOrButton,Task_FlyMap
BPRI0.graphics.townmap.position.cancel.x 0C067A,0C0894,0C3414,0C364C,0C523A
BPRI0.graphics.townmap.position.cancel.y 0C0686,0C0704,0C3416,0C3648,0C5246
# PlaySEForSelectedMapsec,Task_RegionMap,HandleRegionMapInput,SnapToIconOrButton
BPRI0.graphics.townmap.position.switch.x 0C064C,0C0860,0C3418,0C3608
BPRI0.graphics.townmap.position.switch.y 0C0658,0C086C,0C341A,0C360C
# BufferRegionMapBg
BPRI0.graphics.townmap.position.switch.x+3 0C0F02,0C0F04,0C0F18
BPRI0.graphics.townmap.position.switch.y+3 0C0F1A
BPRI0.graphics.townmap.position.switch.y+4 0C0F2E
BPRI0.graphics.townmap.position.switch.y+5 0C0F30
- EV caps: how many EVs you can gain from vitamins, or how many EVs you can have in any stat. -----------------------------------
BPRI0.scripts.ev.cap.vitamins-1 0419FA
BPRI0.scripts.ev.cap.vitamins 041A0A,041A0E
BPRI0.scripts.ev.cap.vitamins-1 041FC4
BPRI0.scripts.ev.cap.vitamins 041FD4,041FD8
BPRI0.scripts.ev.cap.vitamins-1 042810,0429CC
BPRI0.scripts.ev.cap.stat 0438E8,0438EE
- Money cap: The maximum amount of money the player can carry at once. -----------------------------------
# Coins
BPRI0:scripts.coins.cap 0D0768
BPRI0:scripts.coins.cap-1 0D0744
BPRI0:scripts.coins.cap-9 16C7B5
BPRI0:scripts.coins.cap-19 16C86C,16C8CC
BPRI0:scripts.coins.cap-49 16C6E6
BPRI0:scripts.coins.cap-499 16C6B8
- Other -------------------------------------------------------------------------------------------------------------------------
# TryProduceOrHatchEgg
# TryProduceOrHatchEgg,ShouldEggHatch
BPRI0.scripts.daycare.exp.multiplier 0461D8,0462C4 # Picking an even number will prevent new eggs from being created.
# data.battle.text length
BPRI0:data.battle.textlength+11 0D7968
# Intro
BPRI0:scripts.newgame.money 054B6C
BPRI0.scripts.newgame.start.bank 054A10
BPRI0.scripts.newgame.start.map 054A12
BPRI0.scripts.newgame.start.x 054A14
BPRI0.scripts.newgame.start.y 054A0C
BPRI0.scripts.newgame.heal.bank 0BFF1C
BPRI0.scripts.newgame.heal.map 0BFF20
BPRI0.scripts.newgame.heal.x 0BFF24
BPRI0.scripts.newgame.heal.y 0BFF28
# Ruby, Sapphire, and Emerald spawn the player in the middle of the truck instead of predefined coordinates.
BPRI0.scripts.newgame.professor.pokemon 12FBC8,130FD0,130FDC # ID of the pokemon shown during the professor's introduction. Also edit pointers to graphics.pokemon.sprites.front/29 and graphics.pokemon.palettes.normal/29
# Catchmap. See http://sfc.pokefans.net/lesson.php?id=20
BPRI0.data.maps.catchmap.conversion.kanto.length 13CB78
# 2 entries for speed (because double battles), 1 entry each for atk, def, spatk, spdef
BPRI0.scripts.battle.badge.boost 014DB0,014E98,03ED22,03ED54,03ED8A,03EDC2
# lucky egg exp boost, normally 150%
BPRI0.scripts.exp.boost.luckyegg 021D46
# trainer battle exp boost, normall 150%
BPRI0.scripts.exp.boost.trainer 021D62
# Exp Boost for traded pokemon, normally 150%
BPRI0.scripts.exp.boost.traded 021D96
# Nature limiters
# AXVE0.data.pokemon.natures.count XXXXXX,XXXXXX,XXXXXX
# AXVE0.data.pokemon.natures.count-1 XXXXXX,XXXXXX,XXXXXX,XXXXXX
# AXVE0.data.pokemon.natures.count-2 XXXXXX
# AXPE0.data.pokemon.natures.count XXXXXX,XXXXXX,XXXXXX
# AXPE0.data.pokemon.natures.count-1 XXXXXX,XXXXXX,XXXXXX,XXXXXX
# AXPE0.data.pokemon.natures.count-2 XXXXXX
# AXVE1.data.pokemon.natures.count XXXXXX,XXXXXX,XXXXXX
# AXVE1.data.pokemon.natures.count-1 XXXXXX,XXXXXX,XXXXXX,XXXXXX
# AXVE1.data.pokemon.natures.count-2 XXXXXX
# AXPE1.data.pokemon.natures.count XXXXXX,XXXXXX,XXXXXX
# AXPE1.data.pokemon.natures.count-1 XXXXXX,XXXXXX,XXXXXX,XXXXXX
# AXPE1.data.pokemon.natures.count-2 XXXXXX
# BPRE0.data.pokemon.natures.count XXXXXX,XXXXXX,XXXXXX
# BPGE0.data.pokemon.natures.count XXXXXX,XXXXXX,XXXXXX
# BPRE1.data.pokemon.natures.count XXXXXX,XXXXXX,XXXXXX
# BPGE1.data.pokemon.natures.count XXXXXX,XXXXXX,XXXXXX
# BPEE0.data.pokemon.natures.count XXXXXX,XXXXXX,XXXXXX
# BPEE0.data.pokemon.natures.count-1 XXXXXX,XXXXXX,XXXXXX,XXXXXX
# BPEE0.data.pokemon.natures.count-2 XXXXXX
# type limiter. See LoadMoveInfoUI and DrawMoveInfoUIMarkers
BPRI0.data.pokemon.type.length 44E058
BPRI0.data.pokemon.type.length+1 0E566C,1334E4
BPRI0.data.pokemon.type.length+2 0E5678,1334F0
BPRI0.data.pokemon.type.length+3 0E5684,1334FC
BPRI0.data.pokemon.type.length+4 0E5690,133508
BPRI0.data.pokemon.type.length+5 0E569C
# number of item effects that link to type boost. See sHoldEffectToType
BPRI0.data.pokemon.type.holdEffect.length-1 03EE2C
# trainer phone calls
# evolution methods limiter
BPRI0.scripts.evolution.count-1 042E96
# item count limiter
BPRI0:data.items.count 098A88,09B4A0,10870C,10DAD8,13D504
# data.abilities.pickup.items limiter
BPRI0.data.abilities.pickup.length-2 02CE70
# data.maps.heal.map limiter
BPRI0.data.maps.heal.length-1 0BFE2C
BPRI0.data.maps.heal.length 0BFE66
# maximum flash level
BPRI0.scripts.moves.flash.maxlevel-1 3BF8E8
# number of balls in the safari zone
BPRI0.scripts.games.safari.balls.count 0A0F9E
# overworld table limiters (credit to phoenixbound)
BPRI0.graphics.overworld.tablelength-1 05F2CC
# firstpersonview table length
BPRI0.graphics.firstpersonview.count-1 0F8398
BPRI0.graphics.firstpersonview.count 0F839C,0F83BA,0F8440,0F87E6,0F880E
BPRI0.data.items.teachy.count 46E68C
# not really sure about this...
BPRI0.scripts.seagallop.count-1 146E88

View File

@ -1421,7 +1421,7 @@ Format = '''[id:: tileset<`lzt4`> tilemap<`lzm4x30x24|table`> palette<`ucp4`>]8'
[[NamedAnchors]]
Name = '''graphics.overworld.firstpersonview.sprites'''
Address = 0x436EBC
Format = '''[id.data.maps.names+88 transition.transitiontype worldmapflag: tileset<`lzt4|graphics.firstpersonview.sprites`> tilemap<`lzm4x32x20|graphics.firstpersonview.sprites`> pal<`ucp4:DE`>]graphics.firstpersonview.count'''
Format = '''[id.data.maps.names+88 transition.transitiontype worldmapflag: tileset<`lzt4|graphics.overworld.firstpersonview.sprites`> tilemap<`lzm4x32x20|graphics.overworld.firstpersonview.sprites`> pal<`ucp4:DE`>]graphics.firstpersonview.count'''
[[NamedAnchors]]
Name = '''graphics.battle.background.sprites'''
@ -4437,13 +4437,9 @@ Name = '''perstepcallbacks'''
Name = '''directions'''
1 = [
'''South''',
'''Down''',
'''North''',
'''Up''',
'''West''',
'''Left''',
'''East''',
'''Right''',
'''Southwest''',
'''Southeast''',
'''Northwest''',

File diff suppressed because it is too large Load Diff

View File

@ -1174,20 +1174,16 @@ Name = '''perstepcallbacks'''
[[List]]
Name = '''directions'''
0 = '''0'''
1 = '''South'''
1 = '''Down'''
2 = '''North'''
2 = '''Up'''
3 = '''West'''
3 = '''Left'''
4 = '''East'''
4 = '''Right'''
5 = [
'''Southwest''',
'''Southeast''',
'''Northwest''',
'''Northeast''',
0 = [
'''0''',
'''South''',
'''North''',
'''West''',
'''East''',
'''Southwest''',
'''Southeast''',
'''Northwest''',
'''Northeast''',
]
[[List]]

View File

@ -258,6 +258,7 @@ double.battle.continue.silent 5C 08 trainer:data.trainers.stats 00 00 star
59 spriteinvisible npc: bank. map. # hides the sprite on the given map by setting its invisibility to true.
5A faceplayer # if the script was called by a person event, make that person face the player
5B spriteface npc: direction.directions
5B turnobject npc: direction.directions
5C trainerbattle 00 trainer:data.trainers.stats arg: start<""> playerwin<"">
5C trainerbattle 01 trainer:data.trainers.stats arg: start<""> playerwin<""> winscript<`xse`> # doesn't play encounter music, continues with winscript

View File

@ -0,0 +1,650 @@
// Contributed by Leonardo Cariaggi (leqo-c) and Francesco Cariaggi (anferico)
// Special thanks to Fred40 for helping with a lot of the FireRed image anchors
// quick reference for formats
// . one byte
// : two bytes
// :: four bytes
// "" pokemon-character-set text, dynamic length
// ""10 10 pokemon-character-set characters, the last one should be an 'end' (FF)
// |h display as hex
// |z signed value (can be negative)
// |t a tuple
// |s switch/record. Value type depends on other fields
// |b[] bit-array (1-bit per value, labeled after an enum or list)
// <> pointer
// `asc`10 10 ascii characters
// `ucp` uncompressed palette
// `lzp4` compressed palette
// `lzs4` compressed sprite
// `lzt4` compressed tiles
// `lzm4` compressed tilemap
// `ucs4x4x4` uncompressed sprite
// `uct` uncompressed tiles
// `ucm` uncompressed tilemap
// `xse` event script
// `bse` battle script
// `ase` animation script
// `tse` trainer AI script
// `pie` pokemon item effect
// `tpt` trainer pokemon team
// `osl` overworld sprite list
// `egg` egg moves
// `blm` block-map (which blocks go where in a map)
// `bls` block-set (which tiles/palettes make up a set of blocks)
// `bld` block-data (flags plus pointers to tileset, palette, blockset, block attributes, and tileset animation)
// `bla` block-attributes
//games, BPRI0,
// FRedIt,
data.pokemon.names , 000144, [name""11]
data.pokemon.moves.names , 000148, ^[name""13]
data.abilities.names , 0001C0, [name""13]
data.trainers.classes.names , 0D7B0C, [name""13]107
data.pokemon.type.names , 0308B4, ^[name""7]data.pokemon.type.length
data.items.stats , 0001C8, [name""13 unused. index: price: holdeffect.holdeffects param. description<""> keyitemvalue. bagkeyitem. pocket.pocketid type.|s=pocket(0=itemtype|1=itemtype|2=itemtype|4=itemtype|5=itemtype) fieldeffect<> battleusage:: battleeffect<> battleextra::]data.items.count
data.abilities.descriptions , 0001C4, [description<"">]data.abilities.names
data.pokemon.moves.descriptions , 0E5664, [description<"">]data.pokemon.moves.names-1
scripts.text.multichoice , 09CC28, [options<[text<""> unused::]/count> count::]
scripts.text.listmenu , 0CBC00, [option1<""> option2<""> option3<""> option4<""> option5<""> option6<""> option7<""> option8<""> option9<""> optionA<""> optionB<""> optionC<"">]7
data.pokemon.natures.names , 0487B0, [name<"">]25
data.menus.text.options , 088EAC, [text<"">]7
data.menus.text.pc , 08FCCC, [text<""> msgvar::messagevars]31
data.menus.text.pokemon , 121E6C, [text<"">]27
data.menus.text.pcoptions , 0EBA30, [text<""> code<>]3
data.menus.namescreen.content , 09F738, [line""8]12
data.menus.namescreen.draw , 09FB30, [line<[unknown:|h gap. character""1]!FF>]12
scripts.newgame.names.male , 131780, [name<"">]19
scripts.newgame.names.female , 13177C, [name<"">]19
scripts.newgame.names.rival , 1317E0, [name<"">]4
data.maps.names , 0C0E44, [name<"">]
data.maps.banks , 055258, [maps<[map<[layout<[width:: height:: borderblock<> blockmap<`blm`> blockdata1<[isCompressed. isSecondary. padding: tileset<> pal<`ucp4:0123456789ABCDEF`> blockset<> animation<> attributes<>]1> blockdata2<[isCompressed. isSecondary. padding: tileset<> pal<`ucp4:0123456789ABCDEF`> blockset<> animation<> attributes<>]1> borderwidth. borderheight. unused:]1> events<[objectCount.100 warpCount.100 scriptCount.100 signpostCount.100 objects<[id. graphics.graphics.overworld.sprites kind: x:|z y:|z elevation.11 moveType. range:|t|x::|y:: trainerType: trainerRangeOrBerryID: script<`xse`> flag:|h padding:]/objectCount> warps<[x:|z y:|z elevation.11 warpID. map. bank.]/warpCount> scripts<[x:|z y:|z elevation:11 trigger: index:: script<`xse`>]/scriptCount> signposts<[x:|z y:|z elevation.11 kind. unused:1 arg::|s=kind(0=<>|1=<>|2=<>|3=<>|4=<>)]/signpostCount>]1> mapscripts<[type. pointer<>]!00> connections<[count:: connections<[direction::mapdirections offset:: mapGroup. mapNum. unused:]/count>]1> music:songnames layoutID:data.maps.layouts+1 regionSectionID.data.maps.names+88 cave. weather. mapType. allowBiking. flags.|t|allowEscaping.|allowRunning.|showMapName. floorNum. battleType.]1>]?>]43
data.maps.layouts , 0551A0, [layout<[width:: height:: borderblock<> blockmap<`blm`> blockdata1<[isCompressed. isSecondary. padding: tileset<> pal<`ucp4:0123456789ABCDEF`> blockset<> animation<> attributes<>]1> blockdata2<[isCompressed. isSecondary. padding: tileset<> pal<`ucp4:0123456789ABCDEF`> blockset<> animation<> attributes<>]1> borderwidth. borderheight. unused:]1>]383
data.maps.dungeons.stats , 0C1A68, [id:: name<""> description<"">]19
data.maps.heal.map , 0BFF30, [bank: map:]data.maps.heal.length
data.maps.heal.healerNPC , 0BFFC4, [ID.]data.maps.heal.map
// TODO Leo: Questo fly.spawn l'ho valorizzato correttamente ma nella rom inglese ci sono anche due copie del puntatore con -8.
data.maps.fly.spawn , 0BFE24, [bank. map. x: y: unused:]data.maps.heal.length
data.maps.fly.connections , 0C5440, [bank. map. flight.]data.maps.names-0-1
data.maps.roaming.sets , 141E88, [loc1. loc2. loc3. loc4. loc5. loc6. loc7.]26
// +28 pokemon are for egg and unown sprites (B-Z, !, ?),
graphics.pokemon.sprites.front , 000128, ^[sprite<`lzs4x8x8`> uncompressedLength: index:]data.pokemon.names+28
graphics.pokemon.sprites.back , 00012C, [sprite<`lzs4x8x8`> uncompressedLength: index:]data.pokemon.names+28
graphics.pokemon.sprites.ghost , 0345A4, `lzs4x8x8|graphics.pokemon.palettes.ghost`
graphics.pokemon.palettes.ghost , 0345AC, `lzp4`
graphics.pokemon.sprites.elevation , 0355E4, [elevation.]data.pokemon.names
graphics.pokemon.sprites.footprint , 105ECC, [footprint<`ucs1x2x2`>]data.pokemon.names+1
graphics.pokemon.sprites.coordinates.front, 011ED0, [size.|t|width::|height:: yOffset. render|render=graphics.battle.background.sprites/0/battlemap/|0|0|240|112|graphics.pokemon.sprites.front|144|8||yOffset-graphics.pokemon.sprites.elevation/elevation unused:]data.pokemon.names+28
graphics.pokemon.sprites.coordinates.back, 074584, [size.|t|width::|height:: yOffset. render|render=graphics.battle.background.sprites/0/battlemap/|0|0|240|112|graphics.pokemon.sprites.back|40|48||yOffset unused:]data.pokemon.names+28
graphics.pokemon.sprites.anchor , 12EB70, [x1. y1. yClose. x2. y2.]data.pokemon.names-1+2
graphics.pokemon.palettes.normal , 000130, ^[palette<`lzp4`> index: unused:]data.pokemon.names+28
graphics.pokemon.palettes.shiny , 000134, [palette<`lzp4`> index: unused:]data.pokemon.names+28
graphics.pokemon.icons.sprites , 000138, [icon<`ucs4x4x8|graphics.pokemon.icons.index`>]data.pokemon.names+28
graphics.pokemon.icons.index , 00013C, [index.graphics.pokemon.icons.palettes]data.pokemon.names+28
graphics.pokemon.icons.palettes , 000140, [palette<`ucp4`> id::]3
graphics.pokemon.icons.deoxys , 3CC8D8, `ucs4x4x16|graphics.pokemon.icons.index`
graphics.pokemon.shiny.star.sprite , 13AF44, `lzs4x1x2|graphics.pokemon.shiny.star.palette`
graphics.pokemon.shiny.star.palette , 13AF48, `ucp4`
graphics.pokemon.evolution.scene.palette, 0D04F4, `ucp4:AB`
graphics.pokemon.evolution.scene.tileset, 0D05AC, `lzt4|graphics.pokemon.evolution.scene.palette`
graphics.pokemon.evolution.scene.tilemap1, 0D05B0, `lzm4x32x32|graphics.pokemon.evolution.scene.tileset`
graphics.pokemon.evolution.scene.tilemap2, 0D05B4, `lzm4x32x32|graphics.pokemon.evolution.scene.tileset`
graphics.trainers.sprites.front , 034628, [sprite<`lzs4x8x8|graphics.trainers.palettes.front`> uncompressedLength: index:]148
graphics.trainers.sprites.back.enter , 10BD1C, [sprite<`ucs4x8x8|graphics.trainers.palettes.back`> a. b. c:]6
graphics.trainers.animations.back , 03F790, [animationPointer<[animation<[frame: time:]!FFFF0000>]2>]graphics.trainers.sprites.back.enter
graphics.trainers.animations.frames , 10BE6C, [animationPointer<[animation<>]1>]graphics.trainers.sprites.front
graphics.trainers.coordinates.front , 037F38, [x. y. unused:]graphics.trainers.sprites.front
graphics.trainers.palettes.front , 034638, [palette<`lzp4`> index: unused:]graphics.trainers.sprites.front
graphics.trainers.sprites.back.throw , 03F78C, [titleTag:|h paletteTag:|h oam<> anims<> sprites<`osl|graphics.trainers.palettes.back:sprite`> affineAnimations<> callback<>]graphics.trainers.sprites.back.enter
graphics.trainers.coordinates.back , 032460, [x. y. unused:]graphics.trainers.sprites.back.enter
graphics.trainers.palettes.back , 0334C0, [pal<`lzp4`> sprite::]graphics.trainers.sprites.back.enter
graphics.trainers.emotes.sprites , 3C039C, [pointer<`ucs4x2x2|graphics.overworld.palettes:id=1100`> length::]15
graphics.pokedex.habitats , 103198, [sprite<`lzs4x8x6`> pal<`ucp4`>]data.pokedex.habitat.names+6
graphics.pokedex.minibox , 105118, `lzs4x8x4|graphics.townmap.catchmap.palette`
graphics.pokedex.regionaldex.tileset , 1028F8, `lzt4|graphics.townmap.catchmap.palette`
graphics.pokedex.nationaldex.tileset , 1027F0, `lzt4|graphics.townmap.catchmap.palette`
graphics.pokemon.moves.animations , 072520, [animation<`ase`>]data.pokemon.moves.names
graphics.moves.particles.sprites-13880 , 072808, [ptr<`lzt4`> size: index:move_particles+10000]move_particles
graphics.moves.particles.palettes-13880 , 07280C, [ptr<`lzp4`> index:move_particles+10000 unused:]graphics.moves.particles.sprites
graphics.moves.backgrounds.all , 0738B0, [tileset<`lzt4`> palette<`lzp4:2`> tilemap<`lzm4x32x32|graphics.moves.backgrounds.all`>]animationbg
graphics.moves.backgrounds.psychic , 3A6E4C, `lzm4x32x20|graphics.moves.backgrounds.all`
graphics.moves.backgrounds.impact.opponent, 3A6E58, `lzm4x32x20|graphics.moves.backgrounds.all`
graphics.moves.backgrounds.impact.player, 3A6E64, `lzm4x32x20|graphics.moves.backgrounds.all`
graphics.moves.backgrounds.impact.contest, 3A6E70, `lzm4x32x20|graphics.moves.backgrounds.all`
graphics.moves.backgrounds.impact.fissure, 3A6F24, `lzm4x32x64|graphics.moves.backgrounds.all`
graphics.moves.surf.palette , 0AB530, `lzp4:8`
graphics.moves.surf.tileset , 0AB528, `lzt4|graphics.moves.surf.palette`
graphics.moves.surf.opponent , 0AB4D0, `lzm4x32x64|graphics.moves.surf.tileset`
graphics.moves.surf.player , 0AB4E0, `lzm4x32x64|graphics.moves.surf.tileset`
graphics.moves.surf.contest , 0AB524, `lzm4x32x64|graphics.moves.surf.tileset`
graphics.items.ball.sprites , 0001D0, [sprite<`lzs4x2x6|graphics.items.ball.palettes`> uncompressedLength: tag:]12
graphics.items.ball.palettes , 0001D4, [palette<`lzp4`> a b unused:]graphics.items.ball.sprites
graphics.items.ball.trade.sprite , 265EB4, `ucs4x2x24|graphics.items.ball.trade.palette`
graphics.items.ball.trade.palette , 265EBC, `ucp4`
graphics.items.sprites , 098A8C, [sprite<`lzs4x3x3`> palette<`lzp4`>]data.items.stats+1
graphics.items.mail , 0BF060, [pal<`ucp4`> tileset<`lzt4`> tilemap<`lzm4x32x20|graphics.items.mail`> size:: textcolor:|c shadow:|c]12
graphics.items.fossils.palette1 , 09D6D4, `ucp4`
graphics.items.fossils.palette2 , 09D624, `ucp4`
graphics.items.fossils.sprite1 , 3D8690, `ucs4x8x8|graphics.items.fossils.palette1`
graphics.items.fossils.sprite2 , 3D8680, `ucs4x8x8|graphics.items.fossils.palette2`
data.pokemon.stats , 0001BC, [hp. attack. def. speed. spatk. spdef. baseStatTotal|=hp+attack+def+speed+spatk+spdef type1.data.pokemon.type.names type2.data.pokemon.type.names catchRate. baseExp. evs:|t|hp:|atk:|def:|spd:|spatk:|spdef: item1:data.items.stats item2:data.items.stats genderratio.genderratiovalues steps2hatch. basehappiness. growthrate.growthrates egg1.egggroups egg2.egggroups ability1.data.abilities.names ability2.data.abilities.names runrate. dex.|t|color:::.bodycolors|noFlip. padding:]data.pokemon.names
// TODO Leo: dati trovati, nessun anchor
data.pokemon.deoxys.stats , 0445F0, [hp: atk: def: spd: spatk: spdef:]1
data.pokemon.evolutions , 042E58, [method1:evolutionmethods arg1:|s=method1(6=data.items.stats|7=data.items.stats) species1:data.pokemon.names unused1: method2:evolutionmethods arg2:|s=method2(6=data.items.stats|7=data.items.stats) species2:data.pokemon.names unused2: method3:evolutionmethods arg3:|s=method3(6=data.items.stats|7=data.items.stats) species3:data.pokemon.names unused3: method4:evolutionmethods arg4:|s=method4(6=data.items.stats|7=data.items.stats) species4:data.pokemon.names unused4: method5:evolutionmethods arg5:|s=method5(6=data.items.stats|7=data.items.stats) species5:data.pokemon.names unused5:]data.pokemon.names
// TODO Leo: dati trovati, nessun anchor
data.items.pokeball.catchrates , 02D618, [catchrate.]4
data.items.effects , 03A19C, [data<`pie`>]data.items.stats-13-199
data.items.berry.stats , 09C84C, [name""7 firmness. size: maxYield. minYield. description1<""> description2<""> stageDuration. spicy. dry. sweet. bitter. sour. smoothness:]43
data.items.teachy.tv , 46E680, [label<""> id::]data.items.teachy.count
data.pokemon.moves.stats.battle , 0001CC, [effect.moveeffectoptions power. type.data.pokemon.type.names accuracy. pp. effectAccuracy. target|b[]movetarget priority.|z info|b[]moveinfo unused. unused:]data.pokemon.moves.names
data.pokemon.moves.levelup , 03E968, [movesFromLevel<[pair:|t|move::::.data.pokemon.moves.names|level:::.]!FFFF>]data.pokemon.names
data.pokemon.moves.tutors , 120C5C, [move:data.pokemon.moves.names]15
// TODO Leo: dati trovati, nessun anchor
data.pokemon.moves.tutorcompatibility , 120CA8, [moves|b[]data.pokemon.moves.tutors]data.pokemon.names
data.pokemon.moves.tms , 125B1C, [move:data.pokemon.moves.names]58
data.pokemon.moves.tmcompatibility , 043B54, [moves|b[]data.pokemon.moves.tms]data.pokemon.names
data.pokemon.moves.hms , 0440C8, [move:data.pokemon.moves.names]8
data.pokemon.moves.details.flash.radius , 07EFC0, [levelToRadius:]scripts.moves.flash.maxlevel
data.pokemon.moves.details.lowkick.power, 02C8B4, [weight: basePower:]!FFFFFFFF
data.pokemon.moves.details.singing , 072638, [move:data.pokemon.moves.names]!FFFF
// TODO Leo: dati trovati, nessun anchor (3F5FD7). Tuttavia, sembra abbia risolto.
data.pokemon.moves.details.fallback.names, 0D77C4, [name<"">]data.pokemon.type.names
data.trainers.stats , 00FB84, [structType.trainerStructType class.data.trainers.classes.names introMusicAndGender.|t|music:::.encountersongs|female. sprite.graphics.trainers.sprites.front name""12 prizeMoney|=4*(data.trainers.money/class=class)/moneyrate*pokemon/last/level item1:data.items.stats item2:data.items.stats item3:data.items.stats item4:data.items.stats doubleBattle::doublebattleflag ai|b[]traineraibits | pokemonCount:: pokemon<`tpt`>]
data.trainers.money , 025950, [class.data.trainers.classes.names moneyrate. unused:]105
data.trainers.vsseeker , 10C8B0, [match1:data.trainers.stats match2:data.trainers.stats match3:data.trainers.stats match4:data.trainers.stats match5:data.trainers.stats match6:data.trainers.stats mapbank: map:]221
data.decorations.stats , 00014C, [id. name""16 permission.decorpermissions shape.decorshape category.decorcategory price: unused: description<""> graphics<>]
data.pokemon.wild , 0828E0, [bank. map. unused: grass<[rate:: list<[basic|comment=0|20% common|comment=2|10% uncommon|comment=6|5% rare|comment=8|4% mythic|comment=10|1% lowLevel. highLevel. species:data.pokemon.names]12>]1> surf<[rate:: list<[lowLevel. highLevel. species:data.pokemon.names]5>]1> tree<[rate:: list<[lowLevel. highLevel. species:data.pokemon.names]5>]1> fish<[rate:: list<[old|comment=0|old_rod: good|comment=2|good_rod: super|comment=5|super_rod: lowLevel. highLevel. species:data.pokemon.names]10>]1>]!FFFF
data.pokemon.trades , 053AE0, [nickname""12 receive:data.pokemon.names hp. attack. defense. speed. spatk. spdef. abilitynum:: trainerid:: cool. tough. beauty. smart. cute. unused. unused: personality:: nature|=personality%25|data.pokemon.natures.names helditem:data.items.stats mailnum. trainername""11 trainergender.trainergender sheen. give::data.pokemon.names]9
data.pokemon.moves.details.mimic.metronome.forbidden, 0294F4, [move:data.pokemon.moves.names]!FFFF
data.pokemon.moves.details.protect.successrate, 026FCC, [rate:]4
data.pokemon.friendship.changes , 043778, [lowFriendshipChange.|z mediumFriendshipChange.|z highFriendshipChange.|z]friendshipevents
data.abilities.pickup.items , 02CE68, [item:data.items.stats chance:]data.abilities.pickup.length
data.abilities.soundproof.moves , 01A600, [move:data.pokemon.moves.names]!FFFF
data.pokemon.moves.egg , 045B40, `egg`
data.pokemon.type.chart , 01E8C8, [attack.data.pokemon.type.names defend.data.pokemon.type.names strength.effectiveness]!FFFF00
data.pokemon.type.holdEffects , 03EE24, [effect. type.data.pokemon.type.names]data.pokemon.type.holdEffect.length
data.pokemon.natures.stats , 0435C4, [attack.|z defense.|z speed.|z spAttack.|z spDefense.|z]data.pokemon.natures.names
scripts.moves.effects , 0162E8, [effect<`bse`>]moveeffectoptions
scripts.moves.setupeffects , 1D8F02, [effects.moveeffectoptions]!FF
scripts.commands.battle.animationscript , 0727A8, [code<>]48
scripts.commands.events.specials , 069EF8, [code<>]444
scripts.commands.events.thumb , 069A78, [code<>]213
scripts.specials.vars , 06E470, [variable::|h]21
scripts.commands.battle.battlescript , 014BA0, [code<>]248
scripts.commands.battle.ai_script , 0C7278, [command<>]94
scripts.commands.battle.animations , 3A6F64, [code<>]48
scripts.battle.ai.hpaware.discourage.when.self.high, 1D8FC7, [effect.moveeffectoptions]!FF
scripts.battle.ai.hpaware.discourage.when.self.medium, 1D8FD6, [effect.moveeffectoptions]!FF
scripts.battle.ai.hpaware.discourage.when.self.low, 1D8FB8, [effect.moveeffectoptions]!FF
scripts.battle.ai.hpaware.discourage.when.target.high, 1D900A, [effect.moveeffectoptions]!FF
scripts.battle.ai.hpaware.discourage.when.target.medium, 1D9019, [effect.moveeffectoptions]!FF
scripts.battle.ai.hpaware.discourage.when.target.low, 1D8FFB, [effect.moveeffectoptions]!FF
scripts.battle.ai.trainer , 0C7250, [ai<`tse`>]traineraibits
scripts.newgame.setflags , 054B78, `xse`
scripts.newgame.pc.item , 0EB8CC, [item:data.items.stats count:]!00000000
scripts.credits.panmap , 0F3E48, [data<[loadmapCommand: bank: map:: x: y: delay:: xspeed: yspeed: length:: end1Command: end2Command: end3Command::]1>]13
scripts.credits.text , 0F41A0, [header<""> text<""> bool::|t|unknown.]
scripts.evolution.routines , 042EAC, [thumb<>]scripts.evolution.count
scripts.commands.events.callstd , 069EFC, [script<`xse`>]10
graphics.pokemon.castform.sprite.coordinates.front, 074670, [size. yOffset. unused:]4
graphics.pokemon.castform.sprite.elevations, 0746D0, [elevation.]graphics.pokemon.castform.sprite.coordinates.front
graphics.pokemon.castform.sprite.coordinates.back, 0745A0, [yOffset.]graphics.pokemon.castform.sprite.coordinates.front
data.battle.text , 0CF174, [text<"">]data.battle.textlength
data.pokedex.regional , 0430DC, [index:]data.pokemon.names-1
data.pokedex.national , 043128, [index:]data.pokemon.names-1
// hoenn[treecko] = 1, national[treecko] = 252, HoeennToNationalDex[ 1]= 252
// hoenn[bulbasaur]= 203, national[bulbasaur]= 1, HoennToNationalDex[203]= 1
// -> this table's values can be determined automatically based on the first two
data.pokedex.hoennToNational , 043174, [index:]data.pokemon.names-1
data.pokedex.stats , 088F04, [species""12 height: heightInches|=height÷.254 weight: weightLbs|=weight÷4.536 description1<""> description2<""> unused: pokemonScale: pokemonOffset:|z trainerScale: trainerOffset:|z unused:]
data.pokedex.search.alpha , 1038E8, [species:data.pokedex.national]data.pokedex.national
data.pokedex.search.weight , 103A20, [species:data.pokedex.national]data.pokedex.national-25
data.pokedex.search.size , 103ABC, [species:data.pokedex.national]data.pokedex.national-25
data.pokedex.search.type , 103988, [species:data.pokemon.names]data.pokedex.national
data.pokemon.type.unionroom.options , 44E04C, [type<> index::]data.pokemon.type.names-1+1
data.pokedex.habitat.names , 105230, [name<"">]
data.pokedex.habitat.pages , 106948, [data<[pokemon<[species:data.pokemon.names]/pokecount> pokecount::]/count> count::]data.pokedex.habitat.names
data.battletower.items , 0E65A0, [item:data.items.stats]64
data.battletower.pokemon.level50 , 0E659C, [species:data.pokemon.names heldItem.data.battletower.items flags.|h move1:data.pokemon.moves.names move2:data.pokemon.moves.names move3:data.pokemon.moves.names move4:data.pokemon.moves.names evSpread|b[]battletowerEvFlags nature.data.pokemon.natures.names unused:]300
data.battletower.pokemon.level100 , 0E6540, [species:data.pokemon.names heldItem.data.battletower.items flags.|h move1:data.pokemon.moves.names move2:data.pokemon.moves.names move3:data.pokemon.moves.names move4:data.pokemon.moves.names evSpread|b[]battletowerEvFlags nature.data.pokemon.natures.names unused:]300
data.battletower.prizes , 15E0E0, [item:data.items.stats]15
data.trainers.trainertower.trainers , 15DA04, [trainer<[id. floor. challengetype. prize. name""11 class. textColor. unused. beforeWord1:|h beforeWord2:|h beforeWord3:|h beforeWord4:|h beforeWord5:|h beforeWord6:|h winWord1:|h winWord2:|h winWord3:|h winWord4:|h winWord5:|h winWord6:|h lostWord1:|h lostWord2:|h lostWord3:|h lostWord4:|h lostWord5:|h lostWord6:|h afterWord1:|h afterWord2:|h afterWord3:|h afterWord4:|h afterWord5:|h afterWord6:|h unused: [species:data.pokemon.names heldItem:data.items.stats move1:data.pokemon.moves.names move2:data.pokemon.moves.names move3:data.pokemon.moves.names move4:data.pokemon.moves.names unknown: hpEv. atkEv. defEv. speedEv. spatkEv. spdefEv. otID::|h IVs::|t|hp::.|atk::.|def::.|spd::.|spatk::.|spdef::.|unused.|2ndAbility. personality:: nickname""11 friendship.]6]1>]32
//data.ec.words+8, ,,,, ,,,, 11EAA0, [list<[text<""> a:: b::]/count> count: other:]21 // before1-lose6 use the high 7 bits to select a group, and the low 9 bits to select a phrase.
graphics.pokemon.type.icons , 107E70, `ucs4x16x16|graphics.pokemon.type.palettes`
graphics.pokemon.type.pokeball.palettes , 107E10, `ucp4`
graphics.pokemon.type.palettes , 107E24, `ucp4`
graphics.pokemon.type.map , 107E6C, [width. height. xy:|t|:|x:|.|y::]data.pokemon.type.names+1+5
graphics.text.importer , 145444, [titleTextPal. bodyTextPal. footerTextPal. stampShadowPal. tileset<`lzt4`> tilemap<`lzm4x30x20|graphics.text.importer`> pal<`ucp4`>]8
graphics.text.box.about , 1130D0, `ucs4x5x4|graphics.text.box.palette`
graphics.text.box.palette , 150154, `ucp4:01234`
graphics.text.box.message , 14FA6C, `uct4x18|graphics.text.box.palette`
graphics.text.box.signpost , 14FAA0, `uct4x19|graphics.text.box.palette`
graphics.text.boxes , 069778, [sprite<`ucs4x3x3`> pal<`ucp4`>]10
graphics.bag.male , 3CCCF4, `lzs4x8x8|graphics.bag.palette`
graphics.bag.female , 3CCCFC, `lzs4x8x8|graphics.bag.palette`
graphics.bag.palette , 3CCD04, `lzp4`
graphics.bag.berrycase.palette , 45B49C, `lzp4`
graphics.bag.berrycase.sprite , 45B494, `lzs4x8x8|graphics.bag.berrycase.palette`
graphics.bag.inside1.palette , 1083C4, `lzp4:012`
graphics.bag.inside1.tileset , 108354, `lzt4|graphics.bag.inside1.palette`
graphics.bag.inside1.tilemap.item , 10837C, `lzm4x32x32|graphics.bag.inside1.tileset`
graphics.bag.inside1.tilemap.deposite , 108390, `lzm4x32x32|graphics.bag.inside1.tileset`
graphics.bag.inside1.berry.palette , 10D8C0, `lzp4:012`
graphics.bag.inside1.berry.tileset , 10D890, `lzt4|graphics.bag.inside1.berry.palette`
graphics.bag.inside1.berry.tilemap , 10D8AC, `lzm4x32x32|graphics.bag.inside1.berry.tileset`
graphics.bag.inside2.palette , 13D248, `lzp4:012`
graphics.bag.inside2.tileset , 13D208, `lzt4|graphics.bag.inside2.palette`
graphics.bag.inside2.tilemap , 13D224, `lzm4x32x32|graphics.bag.inside2.tileset`
graphics.bag.inside3.palette , 09B1D8, `lzp4`
graphics.bag.inside3.tileset , 09B180, `lzt4|graphics.bag.inside3.palette`
graphics.bag.inside3.tilemap1 , 09B188, `lzm4x32x32|graphics.bag.inside3.tileset`
graphics.bag.inside3.tilemap2 , 09B1D0, `lzm4x32x32|graphics.bag.inside3.tileset`
graphics.menu.pokeball.palette , 451458, `lzp4`
graphics.menu.pokeball.large , 451450, `lzs4x4x8|graphics.menu.pokeball.palette`
graphics.menu.pokeball.small , 4514C8, `lzs4x2x4|graphics.menu.pokeball.palette`
graphics.menu.status.palette , 451558, `lzp4`
graphics.menu.status.sprite , 451550, `lzs4x4x8|graphics.menu.status.palette`
graphics.menu.bar.palette , 13AA54, `ucp4`
graphics.menu.bar.hp , 13A59C, `lzs4x12x1|graphics.menu.bar.palette`
graphics.menu.bar.exp , 13AA50, `lzs4x12x1|graphics.menu.bar.palette`
graphics.menu.summaryscreen.palette , 136084, `ucp4:0123456`
graphics.menu.summaryscreen.tileset , 1360D8, `lzt4|graphics.menu.summaryscreen.palette`
graphics.menu.summaryscreen.tilemap.info, 135E98, `lzm4x32x20|graphics.menu.summaryscreen.tileset`
graphics.menu.summaryscreen.tilemap.skills, 135E9C, `lzm4x32x20|graphics.menu.summaryscreen.tileset`
graphics.menu.summaryscreen.tilemap.knownmoves, 135E64, `lzm4x32x32|graphics.menu.summaryscreen.tileset`
graphics.menu.summaryscreen.tilemap.egg , 135E2C, `lzm4x32x20|graphics.menu.summaryscreen.tileset`
graphics.menu.summaryscreen.tilemap.changemoves, 135E68, `lzm4x32x20|graphics.menu.summaryscreen.tileset`
graphics.menu.relearner.palette , 0E49A4, `ucp4`
graphics.menu.relearner.tileset , 0E49A8, `lzt4|graphics.menu.relearner.palette`
graphics.menu.relearner.tilemap , 0E49AC, `lzm4x32x32|graphics.menu.relearner.tileset`
graphics.menu.summaryscreen.tilemap.relearner, 135DF0, `lzm4x32x32|graphics.menu.summaryscreen.tileset`
graphics.menu.summaryscreen.unknown.tilemap3, 135E08, `lzm4x32x20|graphics.menu.summaryscreen.tileset`
graphics.menu.partyscreen.selection.palette, 13A1A4, `ucp4`
graphics.menu.partyscreen.selection.sprite1, 13A19C, `lzs4x8x8|graphics.menu.partyscreen.selection.palette`
graphics.menu.partyscreen.selection.sprite2, 13A1A0, `lzs4x8x8|graphics.menu.partyscreen.selection.palette`
graphics.menu.pokemon.background.palette, 11F06C, `lzp4:0123456789A`
graphics.menu.pokemon.background.tileset, 11F028, `lzt4|graphics.menu.pokemon.background.palette`
graphics.menu.pokemon.background.tilemap, 11F044, `lzm4x32x32|graphics.menu.pokemon.background.tileset`
graphics.menu.pokemon.item.sprite , 451408, `ucs4x1x2|graphics.menu.pokemon.item.palette`
graphics.menu.pokemon.item.palette , 451410, `ucp4`
graphics.menu.boxes.background , 091A20, [tileset<`lzt4`> tilemap<`lzm4x20x18|graphics.menu.boxes.background`> palette<`ucp4:12`>]16
graphics.menu.boxes.picker.party.palette, 08F810, `ucp4`
graphics.menu.boxes.picker.cursor.palette, 08F1B4, `ucp4`
graphics.menu.boxes.picker.palette , 08F1B8, `ucp4`
graphics.menu.boxes.picker.palette2 , 08F1B4, `ucp4`
graphics.menu.boxes.picker.tileset , 08F138, `lzt4|graphics.menu.boxes.picker.palette`
//graphics.menu.boxes.picker.tilemap, ,,,, 08F038, 08F00C, 08F04C, 08F020, 0CA098, `lzm4x32x20|graphics.menu.boxes.picker.tileset` // BPRE0, needs testing for LG/1.1
// this tilemap assumes the tileset is loaded at index 0x100 instead of index 0x00. The tilemap won't show right unless we support that.
graphics.menu.boxes.hand.palette.normal , 3C7530, `ucp4`
graphics.menu.boxes.hand.palette.catch , 3CBF88, `ucp4`
graphics.menu.boxes.hand.sprite , 3CBF70, `ucs4x4x16|graphics.menu.boxes.hand.palette.normal`
graphics.menu.tms.tileset , 131C00, `lzt4|graphics.menu.tms.palette`
graphics.menu.tms.background , 131C1C, `lzm4x32x32|graphics.menu.tms.tileset`
graphics.menu.tms.case , 131C38, `lzm4x32x32|graphics.menu.tms.tileset`
graphics.menu.tms.palette , 131C58, `lzp4:0123`
graphics.menu.tms.palette2 , 131C68, `lzp4`
graphics.menu.tms.hm_logo , 1336B8, `ucs4x2x2`
graphics.menu.help.palette , 13BA68, `ucp4`
graphics.menu.text.bold.palette , 10B9AC, `ucp4`
graphics.menu.storage.background.palette1, 08F1C4, `ucp4:3`
graphics.menu.storage.background.palette2, 08F1F0, `ucp4:3`
graphics.menu.storage.background.tileset, 08F0C0, `lzs4x4x2|graphics.menu.storage.background.palette1`
graphics.menu.storage.background.tilemap, 08F0C4, `lzm4x32x32|graphics.menu.storage.background.tileset`
graphics.menu.downarrow.palette , 00AD88, `ucp4`
graphics.menu.downarrow.sprite1 , 0054E0, `ucs4x2x4|graphics.menu.downarrow.palette`
graphics.menu.downarrow.sprite2 , 00554C, `ucs4x4x6|graphics.menu.downarrow.palette`
graphics.menu.downarrow.sprite3 , 1E3730, `ucs4x2x2|graphics.menu.downarrow.palette`
graphics.menu.downarrow.sprite4 , 1E3738, `ucs4x2x2|graphics.menu.downarrow.palette`
// from AGSMG
graphics.moves.substitute.sprite.front , 034FE0, `lzs4x8x8|graphics.moves.substitute.palette`
graphics.moves.substitute.palette , 035038, `lzp4`
graphics.moves.substitute.sprite.back , 035030, `lzs4x8x8|graphics.moves.substitute.palette`
graphics.moves.tmcase.sprite , 45A1F4, `lzs4x4x4|graphics.moves.tmcase.palette`
graphics.moves.tmcase.palette , 1339D4, `lzp4`
graphics.moves.tmcase.palette2 , 1339D8, `lzp4`
graphics.moves.tmcase.palettemap , 133864, [offset:|t|::|page::::]data.pokemon.type.names
data.pokemon.type.camouflage , 02D1EC, [type.data.pokemon.type.names]terrains
data.pokemon.moves.details.naturepower.list, 02BDF8, [move:data.pokemon.moves.names]terrains
graphics.gamecorner.game.palette , 1414AC, `ucp4:01234`
graphics.gamecorner.game.tileset , 14149C, `lzt4|graphics.gamecorner.game.palette`
graphics.gamecorner.game.tilemap , 1414A8, `lzm4x32x20|graphics.gamecorner.game.tileset`
graphics.gamecorner.score.palette , 1414B4, `ucp4:789`
graphics.gamecorner.score.tileset , 1414C0, `lzt4|graphics.gamecorner.score.palette`
graphics.gamecorner.score.tilemap , 1414C4, `lzm4x32x20|graphics.gamecorner.score.tileset`
graphics.gamecorner.sprites , 140D2C, [rolls<`lzs4x4x4`> a:: cheer<`lzs4x4x4`> b:: digits<`lzs4x5x4`> c::]1
graphics.gamecorner.palettes , 140D30, [pal<`ucp4`> id::]7
data.gamecorner.payout , 140CC8, [payout:]7
graphics.misc.questionnaire.palette , 100F64, `ucp4`
graphics.misc.questionnaire.tileset , 1002B8, `lzt4|graphics.misc.questionnaire.palette`
graphics.misc.questionnaire.tilemap , 1002BC, `lzm4x32x20|graphics.misc.questionnaire.tileset`
graphics.misc.questionnaire.button.palette, 436E90, `ucp4`
graphics.misc.questionnaire.button.sprite, 436EB8, `lzs4x8x8|graphics.misc.questionnaire.button.palette`
graphics.overworld.firstpersonview.sprites, 0F8390, [id.data.maps.names+88 transition.transitiontype worldmapflag: tileset<`lzt4|graphics.overworld.firstpersonview.sprites`> tilemap<`lzm4x32x20|graphics.overworld.firstpersonview.sprites`> pal<`ucp4:DE`>]graphics.firstpersonview.count
graphics.battle.background.sprites , 00F224, [battletiles<`lzt4`> battlemap<`lzm4x32x64|graphics.battle.background.sprites|battletiles`> | introtiles<`lzt4`> intromap<`lzm4x32x14|graphics.battle.background.sprites|introtiles`> pal<`lzp4:234`>]
graphics.battle.background.fighttype , 00F1D0, [id. entry.graphics.battle.background.sprites unused:]8
graphics.battle.hud.palette , 259340, `ucp4`
graphics.battle.hud.hpbar.sprite , 047FA4, `ucs4x11x6|graphics.battle.hud.hpbar.palette`
graphics.battle.hud.hpbar.palette , 259348, `ucp4`
graphics.battle.hud.hpbox.player , 2592E8, `lzs4x8x16|graphics.battle.hud.palette`
graphics.battle.hud.hpbox.opponent , 2592F0, `lzs4x8x8|graphics.battle.hud.palette`
graphics.battle.hud.hpbox.doublebattle.player, 259300, `lzs4x8x8|graphics.battle.hud.palette`
graphics.battle.hud.hpbox.doublebattle.opponent, 259310, `lzs4x8x8|graphics.battle.hud.palette`
graphics.battle.hud.hpbox.safarizone , 259318, `lzs4x8x16|graphics.battle.hud.palette`
graphics.battle.hud.status , 259584, `ucs4x13x4|graphics.battle.hud.hpbar.palette`
graphics.battle.hud.pokeballbar , 25954C, `lzs4x16x1|graphics.battle.hud.palette`
graphics.battle.hud.idlelevel.palette , 02644C, `ucp4`
graphics.battle.hud.idlelevel.sprite , 026450, `lzs4x12x3|graphics.battle.hud.idlelevel.palette`
graphics.battle.textbox.palette , 00F3E0, `lzp4:01`
graphics.battle.textbox.tileset , 00F3D8, `lzt4|graphics.battle.textbox.palette`
graphics.battle.textbox.tilemap , 00F3DC, `lzm4x32x64|graphics.battle.textbox.tileset`
graphics.battle.animations.status , 078800, [animation<`ase`>]statusanimations
graphics.battle.animations.special , 03403C, [animation<`ase`>]effectanimations
graphics.battle.animations.statchange.palette.p1, 0BB458, `lzp4:8`
graphics.battle.animations.statchange.palette.p2, 0BB450, `lzp4:8`
graphics.battle.animations.statchange.palette.p3, 0BB460, `lzp4:8`
graphics.battle.animations.statchange.palette.p4, 0BB468, `lzp4:8`
graphics.battle.animations.statchange.palette.p5, 0BB4C8, `lzp4:8`
graphics.battle.animations.statchange.palette.p6, 0BB470, `lzp4:8`
graphics.battle.animations.statchange.palette.p7, 0BB478, `lzp4:8`
graphics.battle.animations.statchange.palette.p8, 0BB48C, `lzp4:8`
graphics.battle.animations.statchange.tileset, 0BB424, `lzt4|graphics.battle.animations.statchange.palette.p1`
graphics.battle.animations.statchange.tilemap1, 0BB3D4, `lzm4x32x32|graphics.battle.animations.statchange.tileset`
graphics.battle.animations.statchange.tilemap2, 0BB420, `lzm4x32x32|graphics.battle.animations.statchange.tileset`
graphics.battle.animations.misc , 034124, [pointer<>]miscanimations
graphics.battle.pokemon.shadow , 249AEC, `lzs4x4x1|graphics.battle.hud.palette`
graphics.titlescreen.background.animation.sprite, 3B8BA4, `lzs4x2x2|graphics.titlescreen.background.animation.palette`
graphics.titlescreen.background.animation.palette, 3B8BC4, `ucp4`
graphics.titlescreen.logo.palette , 0789E4, `ucp8`
graphics.titlescreen.logo.tileset , 0789E8, `lzt8|graphics.titlescreen.logo.palette`
graphics.titlescreen.logo.tilemap , 0789EC, `lzm8x32x20|graphics.titlescreen.logo.tileset`
graphics.titlescreen.pokemon.palette , 0789F0, `ucp4:D`
graphics.titlescreen.pokemon.tileset , 0789F4, `lzt4|graphics.titlescreen.pokemon.palette`
graphics.titlescreen.pokemon.tilemap , 0789F8, `lzm4x32x20|graphics.titlescreen.pokemon.tileset`
graphics.titlescreen.publisher.palette , 0789FC, `ucp4:F`
graphics.titlescreen.publisher.tileset , 078A00, `lzt4|graphics.titlescreen.publisher.palette`
graphics.titlescreen.publisher.tilemap , 078A04, `lzm4x32x20|graphics.titlescreen.publisher.tileset`
graphics.titlescreen.widescreen.tileset , 078A08, `lzt4|graphics.titlescreen.publisher.palette`
graphics.titlescreen.widescreen.tilemap , 078A0C, `lzm4x32x20|graphics.titlescreen.widescreen.tileset`
graphics.titlescreen.introscene.gengar.palette, 403644, `ucp4:5`
graphics.titlescreen.introscene.nidorino.palette, 40364C, `ucp4`
graphics.titlescreen.introscene.grass.palette, 403654, `ucp4`
graphics.titlescreen.introscene.gengar.sprite, 40360C, `lzs4x8x8|graphics.titlescreen.introscene.gengar.palette`
graphics.titlescreen.introscene.nidorino.sprite, 403614, `lzs4x8x8|graphics.titlescreen.introscene.nidorino.palette`
graphics.titlescreen.introscene.nidorino.palette1, 0ED52C, `ucp4:6`
graphics.titlescreen.introscene.nidorino.tileset, 0ED53C, `lzt4|graphics.titlescreen.introscene.nidorino.palette1`
graphics.titlescreen.introscene.nidorino.tilemap, 0ED540, `lzm4x32x32|graphics.titlescreen.introscene.nidorino.tileset`
graphics.titlescreen.introscene.gengar.tileset, 0ED544, `lzt4|graphics.titlescreen.introscene.gengar.palette`
graphics.titlescreen.introscene.gengar.tilemap, 0ED548, `lzm4x32x32|graphics.titlescreen.introscene.gengar.tileset`
graphics.titlescreen.introscene.gengar.tileset2, 0ED814, `lzt4|graphics.titlescreen.introscene.gengar.palette`
graphics.titlescreen.introscene.gengar.tilemap2, 0ED818, `lzm4x32x64|graphics.titlescreen.introscene.gengar.tileset2`
graphics.titlescreen.introscene.nidorino.sprite2, 40361C, `lzs4x8x8|graphics.titlescreen.introscene.nidorino.palette`
graphics.titlescreen.introscene.grass.sprite, 403624, `lzs4x8x4|graphics.titlescreen.introscene.grass.palette`
graphics.titlescreen.introscene.gengar.sprite2, 40362C, `lzs4x8x8|graphics.titlescreen.introscene.gengar.palette`
graphics.titlescreen.introscene.slash.tiles, 403634, `lzt4`
graphics.titlescreen.introscene.forest.palette, 0ED7AC, `ucp4:12`
graphics.titlescreen.introscene.forest.tileset, 0ED7BC, `lzt4|graphics.titlescreen.introscene.forest.palette`
graphics.titlescreen.introscene.forest.tilemap, 0ED7C0, `lzm4x32x20|graphics.titlescreen.introscene.forest.tileset`
graphics.titlescreen.introscene.forest.palette2, 0ED524, `ucp4:123`
graphics.titlescreen.introscene.forest.tileset2, 0ED434, `lzt4|graphics.titlescreen.introscene.forest.palette2`
graphics.titlescreen.introscene.forest.tilemap2, 0ED438, `lzm4x32x64|graphics.titlescreen.introscene.forest.tileset2`
graphics.titlescreen.introscene.grass.palette1, 0ED170, `ucp4:1`
graphics.titlescreen.introscene.grass.tileset, 0ED1C4, `lzt4|graphics.titlescreen.introscene.grass.palette1`
graphics.titlescreen.introscene.grass.tilemap, 0ED1C8, `lzm4x32x64|graphics.titlescreen.introscene.grass.tileset`
graphics.titlescreen.introscene.grass.tileset2, 0ED534, `lzt4|graphics.titlescreen.introscene.forest.palette2`
graphics.titlescreen.introscene.grass.tilemap2, 0ED538, `lzm4x32x20|graphics.titlescreen.introscene.grass.tileset2`
graphics.titlescreen.introscene.grass.background.palette, 0ED174, `ucp4:2`
graphics.titlescreen.introscene.grass.background.tileset, 0ED180, `lzt4|graphics.titlescreen.introscene.grass.background.palette`
graphics.titlescreen.introscene.grass.background.tilemap, 0ED184, `lzm4x32x64|graphics.titlescreen.introscene.grass.background.tileset`
graphics.titlescreen.developer.palette1 , 403318, `ucp4`
graphics.titlescreen.developer.palette2 , 403320, `ucp4`
graphics.titlescreen.developer.palette3 , 403328, `ucp4`
graphics.titlescreen.developer.bigstar , 4032F0, `lzs4x2x2|graphics.titlescreen.developer.palette1`
graphics.titlescreen.developer.littlestar, 4032F8, `lzs4x1x1|graphics.titlescreen.developer.palette2`
graphics.titlescreen.developer.mediumstar, 403300, `lzs4x4x4|graphics.titlescreen.developer.palette2`
graphics.titlescreen.developer.logo , 403308, `lzs4x4x8|graphics.titlescreen.developer.palette3`
graphics.titlescreen.developer.presents , 403310, `lzs4x8x1|graphics.titlescreen.developer.palette3`
graphics.titlescreen.developer.text , 0ECD90, `lzs4x18x2|graphics.titlescreen.developer.palette3`
graphics.titlescreen.copyright.tileset , 0EC834, `lzt4|graphics.titlescreen.copyright.palette`
graphics.titlescreen.copyright.tilemap , 0EC838, `lzm4x32x32|graphics.titlescreen.copyright.tileset`
graphics.titlescreen.copyright.palette , 0EC83C, `ucp4`
graphics.newgame.platform.palette , 459F00, `ucp4`
graphics.newgame.platform.sprite , 459EF0, `lzs4x4x12|graphics.newgame.platform.palette`
graphics.newgame.pikachu.palette , 459EF8, `ucp4`
graphics.newgame.pikachu.body , 459ED8, `lzs4x4x8|graphics.newgame.pikachu.palette`
graphics.newgame.pikachu.ears , 459EE0, `lzs4x4x4|graphics.newgame.pikachu.palette`
graphics.newgame.pikachu.eyes , 459EE8, `lzs4x2x2|graphics.newgame.pikachu.palette`
graphics.newgame.player.male.palette , 1312B8, `ucp4:45`
graphics.newgame.player.male.sprite , 1312BC, `lzs8x8x12|graphics.newgame.player.male.palette`
graphics.newgame.player.female.palette , 1312D0, `ucp4:45`
graphics.newgame.player.female.sprite , 1312D4, `lzs8x8x12|graphics.newgame.player.female.palette`
graphics.newgame.rival.palette , 1312F0, `ucp4:67`
graphics.newgame.rival.sprite , 1312F4, `lzs8x8x12|graphics.newgame.rival.palette`
graphics.newgame.professor.palette , 131390, `ucp4:67`
graphics.newgame.professor.sprite , 131394, `lzs8x8x12|graphics.newgame.professor.palette`
graphics.newgame.background.palette , 130E5C, `ucp4:0123`
graphics.newgame.background.tileset1 , 12EDF8, `lzt4|graphics.newgame.background.palette`
graphics.newgame.background.tilemap1 , 12F518, `lzm4x30x18|graphics.newgame.background.tileset1`
graphics.newgame.background.tileset2 , 130E74, `lzt4|graphics.newgame.background.palette`
graphics.newgame.background.tilemap2 , 130EC4, `lzm4x32x20|graphics.newgame.background.tileset2`
graphics.newgame.menu.naming.palette1 , 09F9CC, `ucp4:0123`
graphics.newgame.menu.naming.palette2 , 3D95C4, `ucp4:01`
graphics.newgame.menu.naming.palette3 , 09F9D0, `ucp4`
graphics.newgame.menu.naming.sprite1 , 3D954C, `ucs4x5x18|graphics.newgame.menu.naming.palette2`
graphics.newgame.menu.naming.sprite2 , 3D93F4, `ucs4x2x6|graphics.newgame.menu.naming.palette1`
graphics.newgame.menu.naming.tileset , 09F980, `lzt4|graphics.newgame.menu.naming.palette1`
// graphics.newgame.menu.naming.tilemap.m1, , , , , 09DF08, 09DEDC, 09DF1C, 09DEF0, , `lzm4x32x20|graphics.newgame.menu.naming.tileset` // has palette issue: wants to use palette 15?
graphics.newgame.menu.naming.tilemap.m2 , 09DFE8, `lzm4x32x20|graphics.newgame.menu.naming.tileset`
graphics.newgame.menu.naming.tilemap.m3 , 09DFE4, `lzm4x32x20|graphics.newgame.menu.naming.tileset`
graphics.newgame.menu.naming.tilemap.m4 , 3D9314, `lzm4x32x20|graphics.newgame.menu.naming.tileset`
graphics.newgame.menu.choosesave.palette, 00C43C, `ucp4`
// Introductory Speeches
scripts.newgame.professor.speeches.intro.welcome, 12F980, ""
scripts.newgame.professor.speeches.intro.preShowcaseMon, 12FA24, ""
scripts.newgame.professor.speeches.intro.showcaseMon, 12FB98, ""
scripts.newgame.professor.speeches.intro.mainSpeech, 12FC2C, ""
scripts.newgame.professor.speeches.intro.introduceSelf, 12FDB4, ""
scripts.newgame.professor.speeches.prompt.gender, 12FEC4, ""
scripts.newgame.professor.speeches.prompt.name, 13019C, ""
scripts.newgame.professor.speeches.prompt.confirmName, 130540, ""
scripts.newgame.professor.speeches.prompt.repromptRivalName, 13036C, ""
scripts.newgame.professor.speeches.prompt.confirmRival, 130588, ""
scripts.newgame.professor.speeches.rememberRivalName, 13068C, ""
scripts.newgame.professor.speeches.intro.rival, 130804, ""
scripts.newgame.professor.speeches.ready, 130954, ""
graphics.townmap.map.palette , 0C049C, `ucp4:01234`
graphics.townmap.map.tileset , 0C04E0, `lzt4|graphics.townmap.map.palette`
graphics.townmap.map.tilemap , 0C050C, `lzm4x30x20|graphics.townmap.map.tileset`
graphics.townmap.islands.tilemap1 , 0C0520, `lzm4x30x20|graphics.townmap.map.tileset`
graphics.townmap.islands.tilemap2 , 0C0538, `lzm4x30x20|graphics.townmap.map.tileset`
graphics.townmap.islands.tilemap3 , 0C0554, `lzm4x30x20|graphics.townmap.map.tileset`
graphics.townmap.border.tileset , 0C04EC, `lzt4|graphics.townmap.map.palette`
graphics.townmap.border.tilemap , 0C0570, `lzm4x30x20|graphics.townmap.border.tileset`
graphics.townmap.border.tileset2 , 0C25F0, `lzt4|graphics.townmap.map.palette`
graphics.townmap.border.tilemap2 , 0C2608, `lzm4x30x20|graphics.townmap.border.tileset2`
graphics.townmap.namesoverlay.xy , 0C3ED4, [x: y:]data.maps.names
graphics.townmap.namesoverlay.widthheight, 0C3ED0, [width: height:]data.maps.names
graphics.townmap.icon.palette , 0C4790, `ucp4`
graphics.townmap.icon.area , 0C4594, `lzs4x1x1|graphics.townmap.icon.palette`
graphics.townmap.icon.fly , 0C4598, `lzs4x2x2|graphics.townmap.icon.palette`
graphics.townmap.icon.indicator , 0C3224, `lzs4x2x2|graphics.townmap.icon.palette`
graphics.townmap.icon.head.male.palette , 0C4498, `ucp4`
graphics.townmap.icon.head.female.palette, 0C44B4, `ucp4`
graphics.townmap.icon.head.male.sprite , 0C43D4, `lzs4x2x2|graphics.townmap.icon.head.male.palette`
graphics.townmap.icon.head.female.sprite, 0C43A4, `lzs4x2x2|graphics.townmap.icon.head.female.palette`
graphics.townmap.catchmap.palette , 102914, `ucp4:0123456789ABCDEF`
graphics.townmap.catchmap.kanto , 106514, `lzs4x12x9|graphics.townmap.catchmap.palette`
graphics.townmap.catchmap.island , 106518, [data<[a b c width height d e f]1> map<`lzt4|graphics.townmap.catchmap.palette`>]7
graphics.townmap.catchmap.shape , 1345B8, [shape.catchmap_shape x. y. unused.]80
graphics.townmap.catchmap.conversion.sevii, 13CBA8, [data<[worldmap:data.maps.names+88 catchmap:]/length> length::]7
graphics.townmap.catchmap.conversion.kanto, 13CBA4, [worldmap:data.maps.names+88 catchmap:]data.maps.catchmap.conversion.kanto.length
graphics.townmap.selector.tilemap , 0C10D4, `lzm4x30x20|graphics.townmap.selector.tileset`
graphics.townmap.selector.tilemap2 , 0C10F4, `lzm4x30x20|graphics.townmap.selector.tileset`
graphics.townmap.selector.tilemap3 , 0C116C, `lzm4x30x20|graphics.townmap.selector.tileset`
graphics.townmap.selector.tileset , 0C1170, `lzt4`
graphics.townmap.opening.left.top , 0C2538, `lzs4x4x8|graphics.townmap.map.palette`
graphics.townmap.opening.left.center , 0C2558, `lzs4x4x8|graphics.townmap.map.palette`
graphics.townmap.opening.left.down , 0C2578, `lzs4x4x8|graphics.townmap.map.palette`
graphics.townmap.opening.right.top , 0C2598, `lzs4x4x8|graphics.townmap.map.palette`
graphics.townmap.opening.right.center , 0C25B8, `lzs4x4x8|graphics.townmap.map.palette`
graphics.townmap.opening.right.down , 0C25D8, `lzs4x4x8|graphics.townmap.map.palette`
graphics.townmap.annotations.kanto , 0C432C, [worldmap.data.maps.names+88]graphics.townmap.map.tilemap*2-4-4-4-1
graphics.townmap.annotations.island1 , 0C4334, [worldmap.data.maps.names+88]graphics.townmap.islands.tilemap1*2-4-4-4-1
graphics.townmap.annotations.island2 , 0C433C, [worldmap.data.maps.names+88]graphics.townmap.islands.tilemap2*2-4-4-4-1
graphics.townmap.annotations.island3 , 0C4364, [worldmap.data.maps.names+88]graphics.townmap.islands.tilemap3*2-4-4-4-1
graphics.credits.trainer.male.palette , 0F4CB0, `ucp4`
graphics.credits.trainer.female.palette , 0F4CDC, `ucp4`
graphics.credits.trainer.rival.palette , 0F4D70, `ucp4`
graphics.credits.trainer.male.sprite , 0F4CA4, `lzs4x8x8|graphics.credits.trainer.male.palette`
graphics.credits.trainer.female.sprite , 0F4CD8, `lzs4x8x8|graphics.credits.trainer.female.palette`
graphics.credits.trainer.rival.sprite , 0F4D6C, `lzs4x8x8|graphics.credits.trainer.rival.palette`
graphics.credits.pokemon.Starter1.idle , 0F4480, `lzs4x10x10|graphics.pokemon.palettes.normal:index=6`
graphics.credits.pokemon.Starter1.animate, 0F4484, `lzs4x12x13|graphics.pokemon.palettes.normal:index=6`
graphics.credits.pokemon.Starter2.idle , 0F44C0, `lzs4x10x10|graphics.pokemon.palettes.normal:index=3`
graphics.credits.pokemon.Starter2.animate, 0F44C4, `lzs4x12x10|graphics.pokemon.palettes.normal:index=3`
graphics.credits.pokemon.Starter3.idle , 0F4508, `lzs4x10x10|graphics.pokemon.palettes.normal:index=9`
graphics.credits.pokemon.Starter3.animate, 0F450C, `lzs4x10x12|graphics.pokemon.palettes.normal:index=9`
graphics.credits.pokemon.Mascot.idle , 0F4570, `lzs4x10x10|graphics.pokemon.palettes.normal:index=25`
graphics.credits.pokemon.Mascot.animate , 0F4574, `lzs4x12x12|graphics.pokemon.palettes.normal:index=25`
graphics.trainercard.badges.palette , 08AEA4, `ucp4`
graphics.trainercard.stickers.palette , 08AEB0, `ucp4`
graphics.trainercard.badges.sprite , 089600, `lzs4x16x2|graphics.trainercard.stickers.palette`
graphics.trainercard.stickers.sprite , 089664, `lzs4x2x8|graphics.trainercard.stickers.palette`
graphics.trainercard.palettes.palette1 , 3C63A0, `ucp4:012`
graphics.trainercard.palettes.palette2 , 3C63A4, `ucp4:012`
graphics.trainercard.palettes.palette3 , 3C63AC, `ucp4:012`
graphics.trainercard.tileset , 089638, `lzt4|graphics.trainercard.palettes.palette1`
graphics.trainercard.tileset2 , 089628, `lzt4|graphics.trainercard.palettes.palette1`
graphics.trainercard.front.tilemap , 0895C4, `lzm4x30x20|graphics.trainercard.tileset`
graphics.trainercard.front.nobadges.tilemap, 0895EC, `lzm4x30x20|graphics.trainercard.tileset`
graphics.trainercard.front.unknown.tilemap, 0895B8, `lzm4x30x20|graphics.trainercard.tileset2`
graphics.trainercard.front.unknown.nobadges.tilemap, 0895E0, `lzm4x30x20|graphics.trainercard.tileset2`
graphics.trainercard.back.tilemap , 08958C, `lzm4x30x20|graphics.trainercard.tileset`
graphics.trainercard.back.unknown.tilemap, 08957C, `lzm4x30x20|graphics.trainercard.tileset2`
graphics.trainercard.background.tilemap , 089558, `lzm4x30x20|graphics.trainercard.tileset`
graphics.trainercard.background.tilemap2, 089548, `lzm4x30x20|graphics.trainercard.tileset2`
graphics.overworld.palettes , 05F4C4, [pal<`ucp4`> id:|h unused:]!0000000000000000
graphics.overworld.palettes2 , 1D6C98, [pal<`ucp4`> id:|h unused:]2
graphics.overworld.sprites , 05F2E0, [data<[starterbytes:|h paletteid:|h secondid:|h length: width: height: info.|t|palSlot::|shadowSize:|inanimate.|reflectionPalette. footprint.owfootprints unused: distribution<> sizedraw<> animation<> sprites<`osl`> ramstore<>]1>]graphics.overworld.tablelength
graphics.overworld.sprites2 , 05DF14, [data<[starterbytes:|h paletteid:|h a<> b<> sprites<`osl|graphics.overworld.palettes2:id=`> d<> e<>]1>]36
graphics.overworld.textcolor , 13CE20, [data.|t|low::|high::]76
graphics.overworld.reflection.palettes.player, 05F61C, [normalPalette:|h padding: pointer<[normalReflection:|h multiplayerReflection:|h unused: unused:]1>]!FF11
graphics.overworld.reflection.palettes.slot10, 05F694, [normalPalette:|h padding: pointer<[normalReflection:|h multiplayerReflection:|h unused: unused:]1>]!FF11
# these should be loaded after the overworld palettes because some hacks change these semi-overworld sprites to use overworld palettes (example, Vega)
graphics.newgame.rival.nameselection.palette, 3D9288, `ucp4`
graphics.newgame.rival.nameselection.sprite, 3D9280, `ucs4x2x36|graphics.newgame.rival.nameselection.palette`
graphics.text.font.other.characters , 00650C, `ucs2x2x1024`
graphics.text.font.other.width , 006514, [width.]512
graphics.text.font.black.characters , 00666C, `ucs2x2x1024`
graphics.text.font.black.width , 006618, [width.]512
graphics.text.font.blue.characters , 0068A0, `ucs2x2x1024`
graphics.text.font.blue.width , 00684C, [width.]512
graphics.text.font.red.characters , 006A0C, `ucs2x2x1024`
graphics.text.font.red.width , 0069B8, [width.]512
graphics.text.font.short.characters , 006444, `ucs2x1x1024`
graphics.text.font.short.width , 00644C, [width.]512
graphics.text.font.japan.short.japanese.characters, 006408, `ucs2x16x64`
graphics.text.font.japan.japan1.characters, 0064B4, `ucs2x16x64`
graphics.text.font.japan.japan2.characters, 0065D0, `ucs2x16x128`
graphics.text.font.japan.japan2.width , 0065D8, [width.]280
graphics.text.font.japan.japan3.characters, 006804, `ucs2x16x128`
graphics.text.font.japan.japan3.width , 00680C, [width.]280
graphics.text.font.japan.japan4.characters, 006970, `ucs2x16x128`
graphics.text.font.japan.japan4.width , 006978, [width.]280
graphics.text.font.japan.japan5.characters, 006A88, `ucs2x32x16`
graphics.text.font.buttons.characters , 006394, `ucs4x16x4|graphics.menu.downarrow.palette`
graphics.text.font.buttons.data , 006390, [tileOffset: width. height.]13
data.text.menu.pokemon.battle , 032B24, ""
data.text.menu.pause , 06EEEC, [text<""> code<>]
data.text.menu.pokemon.options , 120FEC, [text<""> code<>]
data.text.menu.itemStorage , 0EBCA8, [text<""> thumb<>]
sound.fanfares , 071B58, [songID:songnames duration:]14
sound.tracks , 1DA588, [pointer<> musicplayer: unknown:]songnames
sound.pokemon.cry.growl , 072054, ^[type.|h key. length. pan_sweep. p<> attack. decay. sustain. release.]data.pokemon.names-24
sound.pokemon.cry.normal , 072064, ^[type.|h key. length. pan_sweep. p<> attack. decay. sustain. release.]data.pokemon.names-24
sound.pokemon.cry.hoennconversion , 043214, [index:]data.pokemon.names-277
// From Shiny Till Dawn:
data.pokemon.moves.details.flail.chart , 02A450, [hpbenchmark. power.]6
scripts.trig.sinetable.radian , 044D38, [sine:|z]320
scripts.trig.sinetable.degree , 044D90, [sine:|z]180
scripts.commands.buffercommands.player , 02E37C, [thumb<>]57
scripts.commands.buffercommands.opponent, 0359C8, [thumb<>]57
scripts.commands.buffercommands.linkopponent, 03A5B4, [thumb<>]57
scripts.commands.events.onstep.callbacks, 06E788, [thumb<>]perstepcallbacks
scripts.commands.weather.functions , 079CA4, [thumb<>]60
scripts.text.stringvars , 06BC6C, [ram::|h]3
graphics.items.ball.trade.palette , 265EBC, `ucp4`
graphics.items.ball.trade.sprite , 265EB4, `ucs4x2x24|graphics.items.ball.trade.palette`
scripts.text.daycare.compatibility.messages, 0465B4, [pointer<"">]4
graphics.pokemon.palettes.egg , 25920C, `ucp4`
graphics.pokemon.sprites.egg , 2591FC, `ucs4x4x17|graphics.pokemon.palettes.egg`
data.text.trade.messages , 124600, [text<"">]9
data.statstages.default , 014E58, [numerator. denominator. ratio|=numerator÷denominator]13
scripts.seagallop.destinations , 146EF0, [bank. map. x. y.]scripts.seagallop.count
scripts.fromthumb.safari.end , 0A1004, `xse`
scripts.text.names , 12DB90, [text<"">]
scripts.text.interviews , 12CE90, [text<"">]
scripts.text.destinations , 09D8F4, [text<"">]
// From Soup
data.statstages.accuracy , 01E08C, [numerator. divisor. unused:]13
data.statstages.critical , 01E4FC, [rate:]!0000
// From Yogia
graphics.trainers.elite4.mugshot.palettes, 0D2AE0, [palette<`ucp4`>]trainerMugshots
graphics.trainers.players.mugshot.palettes, 0D2AE4, [palette<`ucp4`>]playerMugshots

View File

@ -506,7 +506,7 @@ graphics.misc.questionnaire.button.sprite, ,,,, 43F948, 43F784, 43F9B8, 43F7F4,
graphics.misc.stationary, ,,,, 1462E4,,,, , [id:: tileset<`lzt4`> tilemap<`lzm4x30x24|table`> palette<`ucp4`>]8
graphics.overworld.firstpersonview.sprites, , , , , 0F80FC, 0F80D4, 0F8174, 0F814C, , [id.data.maps.names+88 transition.transitiontype worldmapflag:|h tileset<`lzt4|graphics.firstpersonview.sprites`> tilemap<`lzm4x32x20|graphics.firstpersonview.sprites`> pal<`ucp4:DE`>]graphics.firstpersonview.count
graphics.overworld.firstpersonview.sprites, , , , , 0F80FC, 0F80D4, 0F8174, 0F814C, , [id.data.maps.names+88 transition.transitiontype worldmapflag:|h tileset<`lzt4|graphics.overworld.firstpersonview.sprites`> tilemap<`lzm4x32x20|graphics.overworld.firstpersonview.sprites`> pal<`ucp4:DE`>]graphics.firstpersonview.count
graphics.battle.background.sprites, , , , , 00F2A0, 00F2A0, 00F2B4, 00F2B4, , [battletiles<`lzt4`> battlemap<`lzm4x32x64|graphics.battle.background.sprites|battletiles`> | introtiles<`lzt4`> intromap<`lzm4x32x14|graphics.battle.background.sprites|introtiles`> pal<`lzp4:234`>]
graphics.battle.background.sprites, 00D954, 00D954, 00D954, 00D954, , , , , 035940, [battletiles<`lzt4`> battlemap<`lzm4x32x64|graphics.battle.background.sprites|battletiles`> | introtiles<`lzt4`> intromap<`lzm4x32x32|graphics.battle.background.sprites|introtiles`> pal<`lzp4:234`>]10
graphics.battle.background.fighttype, ,,,, 00F24C, 00F24C, 00F260, 00F260, , [id. entry.graphics.battle.background.sprites unused:]8

View File

@ -91,7 +91,11 @@ namespace HavenSoft.HexManiac.Core.Models {
Ruby1_1 = "AXVE1",
Sapphire1_1 = "AXPE1",
FireRed1_1 = "BPRE1",
LeafGreen1_1 = "BPGE1";
LeafGreen1_1 = "BPGE1",
FireRedFr = "BPRF0",
FireRedIt = "BPRI0",
EmeraldFr = "BPEF0",
EmeraldIt = "BPEI0";
public const string
TmMoves = "data.pokemon.moves.tms",
@ -140,9 +144,10 @@ namespace HavenSoft.HexManiac.Core.Models {
Sapphire1_1,
FireRed1_1,
LeafGreen1_1,
"BPRF0", // french firered
"BPEF0", // french emerald
"BPEI0", // italian emerald
FireRedFr,
FireRedIt,
EmeraldFr,
EmeraldIt,
"ABCD0", // for tests
};
@ -234,8 +239,8 @@ namespace HavenSoft.HexManiac.Core.Models {
"Set Terrain", "Pledge", "Field Effects", "Fling", "Feint", "Attack Blockers", "Type Changes", "Heal Target", "Topsy Turvy Electrify", "Fairy Lock Happy Hour",
"Instruct After You Quash", "Sucker Punch", "Ignore Redirection", "Team Effects", "Camouflage", "Flame Burst", "Last Resort Sky Drop", "Damage Set Terrain", "Teatime",
"RaiseTarget\'sAttackSpAtk2"});
for(var k = 0; k < 7; k++) newList.Add(null); // 246 to 252 are unused.
newList.AddRange(new string[] {"Max Move", "Synchronoise"});
for (var k = 0; k < 7; k++) newList.Add(null); // 246 to 252 are unused.
newList.AddRange(new string[] { "Max Move", "Synchronoise" });
SetList(new NoDataChangeDeltaModel(), MoveEffectListName, newList, null, StoredList.GenerateHash(newList));
}
@ -264,7 +269,7 @@ namespace HavenSoft.HexManiac.Core.Models {
});
SetList(new NoDataChangeDeltaModel(), "holdeffects", newList, null, StoredList.GenerateHash(newList));
}
// Update the type chart effectivenesses list
if (isCFRU && TryGetList("effectiveness", out var multipliers)) {
var newMultipliers = new string[21]; // 0 to 20
@ -377,7 +382,7 @@ namespace HavenSoft.HexManiac.Core.Models {
newList.Add("Acc Up 3");
newList.Add("Evsn Up 3");
SetList(new NoDataChangeDeltaModel(), "zeffects", newList, null, StoredList.GenerateHash(newList));
}
public static StoredMetadata DecodeConstantsFromReference(IReadOnlyList<byte> model, IMetadataInfo info, StoredMetadata metadata, GameReferenceConstants constants) {
@ -535,7 +540,7 @@ namespace HavenSoft.HexManiac.Core.Models {
if (name == ItemEffectsTableName) format = format.Replace("-199", string.Empty); // item effects are as long as the items
// type chart
if (name == "data.pokemon.type.chart") {

View File

@ -381,7 +381,7 @@ namespace HavenSoft.HexManiac.Core.Models {
}
public static bool IsFRLG(this IReadOnlyList<byte> model) {
var code = model.GetGameCode();
return code.StartsWith("BPRE") || code.StartsWith("BPGE");
return code.StartsWith("BPR") || code.StartsWith("BPG");
}
public static bool IsEmerald(this IReadOnlyList<byte> model) => model.GetGameCode() == "BPEE0";
@ -631,7 +631,7 @@ namespace HavenSoft.HexManiac.Core.Models {
model.LoadMetadataProperties(metadata);
}
public static void LoadMetadataProperties(this IDataModel model, StoredMetadata metadata){
public static void LoadMetadataProperties(this IDataModel model, StoredMetadata metadata) {
if (metadata.NextExportID > 0) model.NextExportID = metadata.NextExportID;
if (metadata.FreeSpaceSearch >= 0) model.FreeSpaceStart = Math.Min(model.Count - 1, metadata.FreeSpaceSearch);
if (metadata.FreeSpaceBuffer >= 0) model.FreeSpaceBuffer = metadata.FreeSpaceBuffer;

View File

@ -158,14 +158,46 @@ namespace HavenSoft.HexManiac.Core.Models {
/// </summary>
/// <param name="action"></param>
Task RunBackgroundWork(Action action);
IDelayWorkTimer CreateDelayTimer();
}
public class ImmediateWorkTimer : IDelayWorkTimer {
public bool HasScheduledWork => false;
public DelayWorkResult DelayCall(TimeSpan delay, Action action) {
action.Invoke();
return DelayWorkResult.WorkScheduled;
}
public void Reset() { }
}
public class ManualWorkTimer : IDelayWorkTimer {
Action? work;
public bool HasScheduledWork => work != null;
public DelayWorkResult DelayCall(TimeSpan delay, Action action) {
var result = work == null ? DelayWorkResult.WorkScheduled : DelayWorkResult.WorkScheduledAndPreviousWorkCleared;
work = action;
return result;
}
public void Reset() => work = null;
public void RunWork() {
work?.Invoke();
work = null;
}
}
public class InstantDispatch : IWorkDispatcher {
public static IWorkDispatcher Instance { get; } = new InstantDispatch();
public Task WaitForRenderingAsync() => Task.CompletedTask;
public void BlockOnUIWork(Action action) => action();
public Task DispatchWork(Action action) { action?.Invoke(); return Task.CompletedTask; }
public Task RunBackgroundWork(Action action) => DispatchWork(action);
public IDelayWorkTimer CreateDelayTimer() => new ImmediateWorkTimer();
}
public class ControlledDispatch : IWorkDispatcher {
@ -196,6 +228,14 @@ namespace HavenSoft.HexManiac.Core.Models {
public void RunAllWorkloads() {
while (workloads.Count > 0) RunWorkloadAndContinuations(0);
}
public event EventHandler<ImmediateWorkTimer> DelayTimerRequested;
public IDelayWorkTimer CreateDelayTimer() {
var timer = new ImmediateWorkTimer();
DelayTimerRequested?.Invoke(this, timer);
return timer;
}
}
/// <summary>

View File

@ -57,14 +57,14 @@ namespace HavenSoft.HexManiac.Core.Models.Map {
public int NameIndex {
get {
var code = Element.Model.GetShortGameCode();
int offset = code.IsAny(0x45525042, 0x45475042) ? 88 : 0; // BPRE, BPGE
int offset = code.IsAny(0x45525042, 0x45475042, 0x49525042) ? 88 : 0; // BPRE, BPGE, BPRI
if (!Element.TryGetValue("regionSectionID", out var value)) return -1;
return value - offset;
}
set {
if (!Element.HasField("regionSectionID")) return;
var code = Element.Model.GetShortGameCode();
int offset = code.IsAny(0x45525042, 0x45475042) ? 88 : 0; // BPRE, BPGE
int offset = code.IsAny(0x45525042, 0x45475042, 0x49525042) ? 88 : 0; // BPRE, BPGE, BPRI
Element.SetValue("regionSectionID", value + offset);
}
}
@ -177,7 +177,7 @@ namespace HavenSoft.HexManiac.Core.Models.Map {
int Elevation { get; }
}
public record BaseEventModel(ModelArrayElement Element): IEventModel {
public record BaseEventModel(ModelArrayElement Element) : IEventModel {
public int X => Element.TryGetValue("x", out int x) ? x : 0;
public int Y => Element.TryGetValue("y", out int y) ? y : 0;
public int Elevation => Element.TryGetValue("elevation", out int elevation) ? elevation : 0;
@ -229,6 +229,8 @@ namespace HavenSoft.HexManiac.Core.Models.Map {
public bool HasScript => Kind < 5;
public bool IsHiddenItem => Kind.IsAny(5, 6, 7);
public int ItemValue => Element.Model.ReadMultiByteValue(Element.Start + 8, 2);
public int HiddenItemFlag => Element.Model[Element.Start + 10];
public int HiddenItemCount => Element.Model[Element.Start + 11];
public int ScriptAddress => Element.Model.ReadPointer(Element.Start + 8);
}

View File

@ -64,12 +64,12 @@ namespace HavenSoft.HexManiac.Core.Models {
public static class PCSString {
private const string TextReferenceFileName = "resources/pcsReference.txt";
public static IReadOnlyList<string> PCS;
public static IReadOnlySet<string> ValidInProgressEscapes;
public static IReadOnlyList<byte> Newlines;
public static IReadOnlyDictionary<byte, byte> ControlCodeLengths;
public static IReadOnlyDictionary<string, IReadOnlyDictionary<string, byte[]>> TextMacros;
public static IReadOnlyDictionary<string, IReadOnlyList<IReadOnlyDictionary<string, byte[]>>> TextMacrosIndex;
public static readonly IReadOnlyList<string> PCS;
public static readonly IReadOnlySet<string> ValidInProgressEscapes;
public static readonly IReadOnlyList<byte> Newlines;
public static readonly IReadOnlyDictionary<byte, byte> ControlCodeLengths;
public static readonly IReadOnlyDictionary<string, IReadOnlyDictionary<string, byte[]>> TextMacros;
public static readonly IReadOnlyDictionary<string, IReadOnlyList<IReadOnlyDictionary<string, byte[]>>> TextMacrosIndex;
public static readonly byte DynamicEscape = 0xF7;
public static readonly byte FunctionEscape = 0xFC;
@ -581,6 +581,7 @@ namespace HavenSoft.HexManiac.Core.Models {
}
private static string FindMacro(IReadOnlyList<IReadOnlyDictionary<string, byte[]>> macrosIndex, IReadOnlyList<byte> data, int index) {
if (!index.InRange(0, data.Count)) return null;
var macros = macrosIndex[data[index]];
if (macros == null) return null;
foreach (var kvp in macros) {

View File

@ -35,6 +35,7 @@ namespace HavenSoft.HexManiac.Core.Models.Runs {
public class AutocompleteItem {
public string Text { get; } // text to show in completion tip
public string LineText { get; } // text to use to replace entire line
public int CharacterOffset { get; set; } // character-size offset to use for the autocomplete options
public AutocompleteItem(string text, string lineText) => (Text, LineText) = (text, lineText);
}

View File

@ -154,14 +154,14 @@ namespace HavenSoft.HexManiac.Core.Models.Runs {
private readonly Dictionary<int, ScriptInfo> cachedScripts = new();
public ScriptInfo GetScriptInfo(ScriptParser parser, int scriptStart, CodeBody updateBody, ref int existingSectionCount) {
if (cachedScripts.TryGetValue(scriptStart, out var scriptInfo)) {
if (cachedScripts.TryGetValue(scriptStart, out var scriptInfo) && scriptInfo.Parser == parser) {
existingSectionCount = scriptInfo.SectionCount;
return scriptInfo;
}
var destinations = ScriptDestinations(scriptStart);
var scriptLength = parser.FindLength(model, scriptStart, destinations);
var content = parser.Parse(model, scriptStart, scriptLength, ref existingSectionCount, updateBody);
scriptInfo = new ScriptInfo(scriptStart, scriptLength, content, existingSectionCount);
scriptInfo = new ScriptInfo(scriptStart, scriptLength, parser, content, existingSectionCount);
destinations[scriptStart] = scriptLength;
cachedScripts[scriptStart] = scriptInfo;
return scriptInfo;
@ -248,5 +248,5 @@ namespace HavenSoft.HexManiac.Core.Models.Runs {
public void GotoMap() => JumpAction(new(Group, Map));
}
public record ScriptInfo(int Start, int Length, string Content, int SectionCount) : ISearchTreePayload;
public record ScriptInfo(int Start, int Length, ScriptParser Parser, string Content, int SectionCount) : ISearchTreePayload;
}

View File

@ -275,8 +275,7 @@ namespace HavenSoft.HexManiac.Core.Models.Runs {
} else if (segment.Length == 0) {
continue;
}
var extraWhitespace = new string(' ', longestLabel - segment.Name.Length);
result.Append($"{segment.Name}:{extraWhitespace} {value}");
result.Append($"{segment.Name.PadRight(longestLabel)} : {value}");
if (i < ElementContent.Count - 1) result.AppendLine();
offset += segment.Length;
}
@ -295,7 +294,7 @@ namespace HavenSoft.HexManiac.Core.Models.Runs {
for (int j = 0; j < ElementContent.Count; j++) {
while (fieldIndex < fields.Length && string.IsNullOrWhiteSpace(fields[fieldIndex])) fieldIndex += 1;
if (fieldIndex >= fields.Length) break;
var data = j < fields.Length ? fields[fieldIndex].Split(new[] { ':' }, 2).Last() : string.Empty;
var data = j < fields.Length ? fields[fieldIndex].Split(new[] { ':' }, 2).Last().Trim() : string.Empty;
if (ElementContent[j].Write(this.ElementContent, model, token, Start + segmentOffset, ref data)) {
changeAddresses.Add(Start + segmentOffset);
changedSegments.Add(j);

View File

@ -20,7 +20,9 @@ namespace HavenSoft.HexManiac.Core.Models {
/// </summary>
public class Singletons {
private const string TableReferenceFileName = "resources/tableReference.txt";
private const string TableReferenceFileNameItalian = "resources/tableReference.it.txt";
private const string ConstantReferenceFileName = "resources/constantReference.txt";
private const string ConstantReferenceFileNameItalian = "resources/constantReference.it.txt";
private const string ThumbReferenceFileName = "resources/armReference.txt";
private const string ScriptReferenceFileName = "resources/scriptReference.txt";
private const string BattleScriptReferenceFileName = "resources/battleScriptReference.txt";
@ -137,6 +139,20 @@ namespace HavenSoft.HexManiac.Core.Models {
public static IReadOnlyList<string> ReferenceOrder { get; } = new string[] { "name", Ruby, Sapphire, Ruby1_1, Sapphire1_1, FireRed, LeafGreen, FireRed1_1, LeafGreen1_1, Emerald, "format" };
private IReadOnlyDictionary<string, GameReferenceTables> CreateGameReferenceTables() {
var tableEn = CreateGameReferenceTablesEnglish();
var tableIt = CreateGameReferenceTablesItalian();
var readonlyTables = new Dictionary<string, GameReferenceTables>();
foreach (var pair in tableEn) {
readonlyTables.Add(pair.Key, pair.Value);
}
foreach (var pair in tableIt) {
readonlyTables.Add(pair.Key, pair.Value);
}
return readonlyTables;
}
public static IReadOnlyList<string> ReferenceOrderItalian { get; } = new string[] { "name", FireRedIt, "format" };
private IReadOnlyDictionary<string, GameReferenceTables> CreateGameReferenceTablesEnglish() {
if (!File.Exists(TableReferenceFileName)) return new Dictionary<string, GameReferenceTables>();
var lines = File.ReadAllLines(TableReferenceFileName);
var tables = new Dictionary<string, List<ReferenceTable>>();
@ -172,7 +188,59 @@ namespace HavenSoft.HexManiac.Core.Models {
return readonlyTables;
}
private IReadOnlyDictionary<string, GameReferenceTables> CreateGameReferenceTablesItalian() {
if (!File.Exists(TableReferenceFileNameItalian)) {
return new Dictionary<string, GameReferenceTables>();
}
var lines = File.ReadAllLines(TableReferenceFileNameItalian);
var tables = new Dictionary<string, List<ReferenceTable>>();
for (int i = 0; i < ReferenceOrderItalian.Count - 2; i++) tables[ReferenceOrderItalian[i + 1]] = new List<ReferenceTable>();
foreach (var line in lines) {
var row = line.Trim();
if (row.StartsWith("//")) continue;
var segments = row.Split("//")[0].Split(",");
if (segments.Length != ReferenceOrderItalian.Count) continue;
var name = segments[0].Trim();
var offset = 0;
if (name.Contains("+")) {
var parts = name.Split("+");
name = parts[0];
int.TryParse(parts[1], NumberStyles.HexNumber, CultureInfo.CurrentCulture, out offset);
} else if (name.Contains("-")) {
var parts = name.Split("-");
name = parts[0];
int.TryParse(parts[1], NumberStyles.HexNumber, CultureInfo.CurrentCulture, out offset);
offset = -offset;
}
var format = segments.Last().Trim();
for (int i = 0; i < ReferenceOrderItalian.Count - 2; i++) {
var addressHex = segments[i + 1].Trim();
if (addressHex == string.Empty) continue;
if (!int.TryParse(addressHex, NumberStyles.HexNumber, CultureInfo.CurrentCulture, out int address)) continue;
tables[ReferenceOrderItalian[i + 1]].Add(new ReferenceTable(name, offset, address, format));
}
}
var readonlyTables = new Dictionary<string, GameReferenceTables>();
foreach (var pair in tables) readonlyTables.Add(pair.Key, new GameReferenceTables(pair.Value));
return readonlyTables;
}
private IReadOnlyDictionary<string, GameReferenceConstants> CreateGameReferenceConstants() {
var constantsEn = CreateGameReferenceConstantsEnglish();
var constantsIt = CreateGameReferenceConstantsItalian();
var readonlyConstants = new Dictionary<string, GameReferenceConstants>();
foreach (var pair in constantsEn) {
readonlyConstants.Add(pair.Key, pair.Value);
}
foreach (var pair in constantsIt) {
readonlyConstants.Add(pair.Key, pair.Value);
}
return readonlyConstants;
}
private IReadOnlyDictionary<string, GameReferenceConstants> CreateGameReferenceConstantsEnglish() {
if (!File.Exists(ConstantReferenceFileName)) return new Dictionary<string, GameReferenceConstants>();
var lines = File.ReadAllLines(ConstantReferenceFileName);
var constants = new Dictionary<string, List<ReferenceConstant>>();
@ -194,6 +262,28 @@ namespace HavenSoft.HexManiac.Core.Models {
return readonlyConstants;
}
private IReadOnlyDictionary<string, GameReferenceConstants> CreateGameReferenceConstantsItalian() {
if (!File.Exists(ConstantReferenceFileNameItalian)) return new Dictionary<string, GameReferenceConstants>();
var lines = File.ReadAllLines(ConstantReferenceFileNameItalian);
var constants = new Dictionary<string, List<ReferenceConstant>>();
foreach (var line in lines) {
if (string.IsNullOrWhiteSpace(line)) continue;
var cleanLine = line.Trim();
if (cleanLine.Length < 6) continue;
if (!char.IsLetter(cleanLine[0])) continue;
var gameCode = cleanLine.Substring(0, 5).ToUpper();
if (!constants.TryGetValue(gameCode, out var collection)) {
collection = new List<ReferenceConstant>();
constants[gameCode] = collection;
}
collection.Add(new ReferenceConstant(cleanLine.Substring(5)));
}
var readonlyConstants = new Dictionary<string, GameReferenceConstants>();
foreach (var pair in constants) readonlyConstants.Add(pair.Key, new GameReferenceConstants(pair.Value));
return readonlyConstants;
}
private (IReadOnlyList<ConditionCode>, IReadOnlyList<IInstruction>) LoadThumbReference() {
var conditionalCodes = new List<ConditionCode>();
var instructionTemplates = new List<IInstruction>();
@ -209,7 +299,7 @@ namespace HavenSoft.HexManiac.Core.Models {
public void ExportReadableScriptReference(EditorViewModel editor) {
var specials = new Dictionary<string, StoredList>(
new[] { "axve", "axpe", "bpre", "bpge", "bpee" }
.Select<string, KeyValuePair<string,StoredList>>(
.Select<string, KeyValuePair<string, StoredList>>(
code => new(code, BaseModel.GetDefaultMetadatas(code).SelectMany(md => md.Lists).Single(list => list.Name == "specials"))
)
);

View File

@ -985,123 +985,175 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
var layout = GetLayout();
var (width, height) = (layout.GetValue("width"), layout.GetValue("height"));
var (xx, yy) = ConvertCoordinates(x, y);
xx = xx.LimitToRange(0, width - 1);
yy = yy.LimitToRange(0, height - 1);
xx = xx.LimitToRange(0, width - 2);
yy = yy.LimitToRange(0, height - 2);
Draw9Grid(token, grid, xx, yy);
}
public void Draw25Grid(ModelDelta token, int[,] grid, double x, double y) {
var (xx, yy) = ConvertCoordinates(x, y);
Draw25Grid(token, grid, xx, yy);
}
public void DiscoverCornersFor9Grid(int[,] grid) {
innerCornersFor9Grid = null;
var layout = new LayoutModel(GetLayout());
public void Draw9Grid(ModelDelta token, int[,] grid, int xx, int yy) {
var targets = new List<int>();
for (int x = 0; x < 3; x++) for (int y = 0; y < 3; y++) targets.Add(grid[x, y] & 0x3FF);
// numpad layout for easy reference
var np7 = grid[0, 0] & 0x3FF;
var np8 = grid[1, 0] & 0x3FF;
var np9 = grid[2, 0] & 0x3FF;
var np4 = grid[0, 1] & 0x3FF;
var np5 = grid[1, 1] & 0x3FF;
var np6 = grid[2, 1] & 0x3FF;
var np1 = grid[0, 2] & 0x3FF;
var np2 = grid[1, 2] & 0x3FF;
var np3 = grid[2, 2] & 0x3FF;
var layout = GetLayout();
var (width, height) = (layout.GetValue("width"), layout.GetValue("height"));
var start = layout.GetAddress("blockmap");
// histograms for inside corners, where the ! is a 'hole':
// 7 . 9
// . ! .
// 1 . 3
var corner7 = new AutoDictionary<int, int>(_ => 0);
var corner9 = new AutoDictionary<int, int>(_ => 0);
var corner1 = new AutoDictionary<int, int>(_ => 0);
var corner3 = new AutoDictionary<int, int>(_ => 0);
int get(Point p) => p.X < 0 || p.Y < 0 || p.X >= width || p.Y >= height ? -1 : model.ReadMultiByteValue(start + (p.Y * width + p.X) * 2, 2) & 0x3FF;
void set(Point p, int block) => model.WriteMultiByteValue(start + (p.Y * width + p.X) * 2, 2, token, block);
// use maps in the game to figure out the corners
foreach (var map in GetAllMaps()) {
if (map.Layout.PrimaryBlockset.Start != layout.PrimaryBlockset.Start && map.Layout.SecondaryBlockset.Start != layout.SecondaryBlockset.Start) continue;
// change all connected blocks based on the grid
var todo = new List<Point> { new(xx, yy), new(xx - 1, yy), new(xx + 1, yy), new(xx, yy - 1), new(xx, yy + 1) };
lock (pixelWriteLock) {
set(todo[0], grid[1, 1]);
foreach (var cell in todo) {
var cellValue = get(cell);
if (!targets.Contains(cellValue)) continue;
var north = targets.Contains(get(new(cell.X, cell.Y - 1)));
var south = targets.Contains(get(new(cell.X, cell.Y + 1)));
var west = targets.Contains(get(new(cell.X - 1, cell.Y)));
var east = targets.Contains(get(new(cell.X + 1, cell.Y)));
var aggregate = (north ? "N" : " ") + (east ? "E" : " ") + (south ? "S" : " ") + (west ? "W" : " ");
var block = aggregate switch {
" ES " => grid[0, 0],
" ESW" => grid[1, 0],
" SW" => grid[2, 0],
"NES " => grid[0, 1],
"NESW" => grid[1, 1],
"N SW" => grid[2, 1],
"NE " => grid[0, 2],
"NE W" => grid[1, 2],
"N W" => grid[2, 2],
_ => grid[1, 1],
};
set(cell, block);
var blockMap = map.Layout.BlockMap;
for (int y = 0; y < blockMap.Height; y++) {
bool topEdge = y == 0, bottomEdge = y == blockMap.Height - 1;
for (int x = 0; x < blockMap.Width; x++) {
bool leftEdge = x == 0, rightEdge = x == blockMap.Width - 1;
var block = blockMap[x, y].Block; // store tile and collision, but only check that tiles match
if (!rightEdge && !bottomEdge && blockMap[x + 1, y].Tile.IsAny(np3, np2) && blockMap[x, y + 1].Tile.IsAny(np3, np6)) {
corner7[block] += 1;
}
if (!leftEdge && !bottomEdge && blockMap[x - 1, y].Tile.IsAny(np1, np2) && blockMap[x, y + 1].Tile.IsAny(np1, np4)) {
corner9[block] += 1;
}
if (!rightEdge && !topEdge && blockMap[x + 1, y].Tile.IsAny(np8, np9) && blockMap[x, y - 1].Tile.IsAny(np6, np9)) {
corner1[block] += 1;
}
if (!leftEdge && !topEdge && blockMap[x - 1, y].Tile.IsAny(np7, np8) && blockMap[x, y - 1].Tile.IsAny(np4, np7)) {
corner3[block] += 1;
}
}
}
}
ClearPixelCache();
var corners = new int[2, 2];
var defaultBlock = grid[1, 1] & 0x3FF;
var key = corner7.MostCommonKey(); corners[0, 0] = key != 0 ? key : defaultBlock;
key = corner9.MostCommonKey(); corners[1, 0] = key != 0 ? key : defaultBlock;
key = corner1.MostCommonKey(); corners[0, 1] = key != 0 ? key : defaultBlock;
key = corner3.MostCommonKey(); corners[1, 1] = key != 0 ? key : defaultBlock;
innerCornersFor9Grid = corners;
}
public void Draw25Grid(ModelDelta token, int[,] grid, int xx, int yy) {
var targets = new List<int>();
for (int x = 0; x < 5; x++) {
for (int y = 0; y < 5; y++) {
if (x == 0 && y == 0) continue;
if (x == 4 && y == 0) continue;
if (x == 0 && y == 4) continue;
if (x == 4 && y == 4) continue;
targets.Add(grid[x, y] & 0x3FF);
public void PrepareFor9GridDraw(int[,] grid) {
// when we do a 9-grid draw,
// we want to track which draws are part of the current interaction only
var layout = new LayoutModel(GetLayout());
current9gridInteractionMap = new bool[layout.Width, layout.Height];
// NOTE add this code if we want to make new draws connect to existing draws
/*
var corners = this.innerCornersFor9Grid ?? new int[,] { { grid[1, 1], grid[1, 1] }, { grid[1, 1], grid[1, 1] } };
var targets = new HashSet<int>();
for (int y = 0; y < 3; y++) for (int x = 0; x < 3; x++) targets.Add(grid[x, y]);
for (int y = 0; y < 2; y++) for (int x = 0; x < 2; x++) targets.Add(corners[x, y]);
for (int y = 0; y < layout.Height; y++) {
for (int x = 0; x < layout.Width; x++) {
current9gridInteractionMap[x, y] = targets.Contains(layout.BlockMap[x, y].Block);
}
}
//*/
}
public static int Get9GridBlock(bool[,] map, Point p, int[,] grid9, int[,] corners) {
int neighborhood = 0;
for (int y = -1; y < 2; y++) {
for (int x = -1; x < 2; x++) {
if (!(p.X + x).InRange(0, map.GetLength(0)) || !(p.Y + y).InRange(0, map.GetLength(1))) continue;
var bit = 8 - ((y + 1) * 3 + x + 1);
neighborhood |= (map[p.X + x, p.Y + y] ? 1 : 0) << bit;
}
}
var layout = GetLayout();
var (width, height) = (layout.GetValue("width"), layout.GetValue("height"));
var start = layout.GetAddress("blockmap");
return neighborhood switch {
// normal corners - 4 varients of each (2 corners don't matter)
0b110_110_000 => grid9[2, 2], // bottom-right
0b110_110_100 => grid9[2, 2],
0b111_110_100 => grid9[2, 2],
0b111_110_000 => grid9[2, 2],
0b011_011_000 => grid9[0, 2], // bottom-left
0b011_011_001 => grid9[0, 2],
0b111_011_001 => grid9[0, 2],
0b111_011_000 => grid9[0, 2],
0b000_110_110 => grid9[2, 0], // top-right
0b100_110_110 => grid9[2, 0],
0b100_110_111 => grid9[2, 0],
0b000_110_111 => grid9[2, 0],
0b000_011_011 => grid9[0, 0], // top-left
0b001_011_011 => grid9[0, 0],
0b000_011_111 => grid9[0, 0],
0b001_011_111 => grid9[0, 0],
int get(Point p) => p.X < 0 || p.Y < 0 || p.X >= width || p.Y >= height ? -1 : model.ReadMultiByteValue(start + (p.Y * width + p.X) * 2, 2) & 0x3FF;
void set(Point p, int block) => model.WriteMultiByteValue(start + (p.Y * width + p.X) * 2, 2, token, block);
// edges - 4 variants of each (2 corners don't matter)
0b111_111_000 => grid9[1, 2], // bottom
0b111_111_100 => grid9[1, 2],
0b111_111_001 => grid9[1, 2],
0b111_111_101 => grid9[1, 2],
0b000_111_111 => grid9[1, 0], // top
0b100_111_111 => grid9[1, 0],
0b001_111_111 => grid9[1, 0],
0b101_111_111 => grid9[1, 0],
0b011_011_011 => grid9[0, 1], // left
0b111_011_011 => grid9[0, 1],
0b011_011_111 => grid9[0, 1],
0b111_011_111 => grid9[0, 1],
0b110_110_110 => grid9[2, 1], // right
0b111_110_110 => grid9[2, 1],
0b110_110_111 => grid9[2, 1],
0b111_110_111 => grid9[2, 1],
// change all connected blocks based on the grid
var todo = new List<Point> {
new(xx, yy),
new(xx - 1, yy), new(xx + 1, yy), new(xx, yy - 1), new(xx, yy + 1),
new(xx + 1, yy + 1), new(xx + 1, yy - 1), new(xx - 1, yy + 1), new(xx - 1, yy - 1),
// inside corners
0b111_111_110 => corners[0, 0],
0b111_111_011 => corners[1, 0],
0b110_111_111 => corners[0, 1],
0b011_111_111 => corners[1, 1],
_ => grid9[1, 1],
};
}
private bool[,] current9gridInteractionMap;
private int[,]? innerCornersFor9Grid;
// 9-grid is always drawn 2x2
public void Draw9Grid(ModelDelta token, int[,] grid, int x, int y) {
var innerCornersFor9Grid = this.innerCornersFor9Grid ?? new[,] { { grid[1, 1], grid[1, 1] }, { grid[1, 1], grid[1, 1] } }; // copy so that there's no reference changing during the method
var layout = GetLayout();
var (width, height) = (layout.GetValue("width"), layout.GetValue("height"));
current9gridInteractionMap[x, y] = true;
current9gridInteractionMap[x + 1, y] = true;
current9gridInteractionMap[x, y + 1] = true;
current9gridInteractionMap[x + 1, y + 1] = true;
var start = layout.GetAddress("blockmap");
// need to draw these 4 blocks as well as the 12 neighboring blocks (up/down/left/right/diagonal) if they're set to true
lock (pixelWriteLock) {
set(todo[0], grid[1, 1]);
foreach (var cell in todo) {
var cellValue = get(cell);
if (!targets.Contains(cellValue)) continue;
var northwest = targets.Contains(get(new(cell.X - 1, cell.Y - 1)));
var northeast = targets.Contains(get(new(cell.X + 1, cell.Y - 1)));
var southwest = targets.Contains(get(new(cell.X - 1, cell.Y + 1)));
var southeast = targets.Contains(get(new(cell.X + 1, cell.Y + 1)));
var north = targets.Contains(get(new(cell.X, cell.Y - 1)));
var south = targets.Contains(get(new(cell.X, cell.Y + 1)));
var west = targets.Contains(get(new(cell.X - 1, cell.Y)));
var east = targets.Contains(get(new(cell.X + 1, cell.Y)));
var aggregate = (north ? "N" : " ") + (east ? "E" : " ") + (south ? "S" : " ") + (west ? "W" : " ");
var corners = (northwest ? "7" : " ") + (northeast ? "9" : " ") + (southeast ? "3" : " ") + (southwest ? "1" : " ");
// grid[x, y]
var block = aggregate switch {
" ES " => grid[1, 0],
" ESW" => grid[2, 0],
" SW" => grid[3, 0],
"NES " => grid[0, 2],
"NESW" => grid[2, 2],
"N SW" => grid[4, 2],
"NE " => grid[0, 3],
"NE W" => grid[2, 4],
"N W" => grid[4, 3],
_ => grid[1, 1],
};
if ("NW".All(aggregate.Contains) && !corners.Contains('7')) block = grid[1, 1];
if ("NE".All(aggregate.Contains) && !corners.Contains('9')) block = grid[3, 1];
if ("SE".All(aggregate.Contains) && !corners.Contains('3')) block = grid[3, 3];
if ("SW".All(aggregate.Contains) && !corners.Contains('1')) block = grid[1, 3];
set(cell, block);
for (int dy = -1; dy < 3; dy++) {
for (int dx = -1; dx < 3; dx++) {
var (xx, yy) = (x + dx, y + dy);
if (!xx.InRange(0, width - 1) || !yy.InRange(0, height - 1)) continue;
if (!current9gridInteractionMap[xx, yy]) continue;
var chosenBlock = Get9GridBlock(current9gridInteractionMap, new(xx, yy), grid, innerCornersFor9Grid);
model.WriteMultiByteValue(start + (yy * width + xx) * 2, 2, token, chosenBlock);
}
}
}
@ -1751,7 +1803,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
var map = GetMapModel();
var connections = GetConnections(map, group, this.map);
for (int i = 0; i < connections.Count; i++) {
for (int i = 0; i < (connections?.Count ?? 0); i++) {
if (connections[i].Direction != direction || connections[i].MapGroup != mapGroup || connections[i].MapNum != mapNum) continue;
for (int j = i + 1; j < connections.Count; j++) {

View File

@ -60,15 +60,27 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
public static HashSet<int> GetUsedItemFlags(IDataModel model, ScriptParser parser) {
var usedFlags = new HashSet<int>();
var hiddenItemFlagStart = 600;
if (model.IsEmerald()) {
for (int i = 0x15C; i <= 0x1A9; i++) usedFlags.Add(i); // Emerald Match Call flags
for (int i = 0x15C; i <= 0x1A9; i++) usedFlags.Add(i); // Emerald Match Call flags
hiddenItemFlagStart = 500;
} else if (model.IsFRLG()) {
hiddenItemFlagStart = 1000;
}
// object flags
foreach (var element in GetAllEvents(model, "objects")) {
if (!element.HasField("flag")) continue;
usedFlags.Add(element.GetValue("flag"));
}
// hidden item flags
foreach (var element in GetAllEvents(model, "signposts")) {
var signpost = new SignpostEventModel(element);
if (!signpost.IsHiddenItem) continue;
usedFlags.Add(hiddenItemFlagStart + signpost.HiddenItemFlag);
}
foreach (var spot in GetAllScriptSpots(model, parser, GetAllTopLevelScripts(model), 0x29, 0x2A, 0x2B)) {
usedFlags.Add(model.ReadMultiByteValue(spot.Address + 1, 2));
}

View File

@ -643,7 +643,10 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
public object Hover(double x, double y) {
var map = MapUnderCursor(x, y);
if (map == null) return EmptyTooltip;
if (drawMultipleTiles && tilesToDraw != null) {
if (use9Grid && IsValid9GridSelection) {
var p = ToBoundedMapTilePosition(map, x, y, 2, 2);
UpdateHover(map, p.X, p.Y, 2, 2);
} else if (drawMultipleTiles && tilesToDraw != null) {
var p = ToBoundedMapTilePosition(map, x, y, tilesToDraw.GetLength(0), tilesToDraw.GetLength(1));
if (interactionType == PrimaryInteractionType.Draw) {
while (Math.Abs(p.X - drawSource.X) % tilesToDraw.GetLength(0) != 0) p -= new Point(1, 0);
@ -760,7 +763,6 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
if (interactionType == PrimaryInteractionType.Draw) DrawMove(x, y);
if (interactionType == PrimaryInteractionType.RectangleDraw) RectangleDrawMove(x, y);
if (interactionType == PrimaryInteractionType.Draw9Grid) Draw9Grid(x, y);
if (interactionType == PrimaryInteractionType.Draw25Grid) Draw25Grid(x, y);
if (interactionType == PrimaryInteractionType.Event) EventMove(x, y);
}
@ -769,7 +771,6 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
else if (interactionType == PrimaryInteractionType.Draw) DrawUp(x, y);
else if (interactionType == PrimaryInteractionType.RectangleDraw) DrawUp(x, y);
else if (interactionType == PrimaryInteractionType.Draw9Grid) DrawUp(x, y);
else if (interactionType == PrimaryInteractionType.Draw25Grid) DrawUp(x, y);
interactionType = PrimaryInteractionType.None;
}
@ -777,8 +778,10 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
private void DrawDown(double x, double y, PrimaryInteractionStart click) {
interactionType = PrimaryInteractionType.Draw;
if ((click & PrimaryInteractionStart.ControlClick) != 0) interactionType = PrimaryInteractionType.RectangleDraw;
if (use9Grid && IsValid9GridSelection) interactionType = PrimaryInteractionType.Draw9Grid;
if (use25Grid && IsValid25GridSelection) interactionType = PrimaryInteractionType.Draw25Grid;
if (use9Grid && IsValid9GridSelection) {
interactionType = PrimaryInteractionType.Draw9Grid;
primaryMap.PrepareFor9GridDraw(tilesToDraw);
}
var map = MapUnderCursor(x, y);
if ((click & PrimaryInteractionStart.DoubleClick) != 0) {
if (drawBlockIndex < 0 && collisionIndex < 0) {
@ -799,9 +802,8 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
drawSource = ToBoundedMapTilePosition(map, x, y, 1, 1);
lastDraw = drawSource;
if (click == PrimaryInteractionStart.ControlClick) RectangleDrawMove(x, y);
if (click == PrimaryInteractionStart.Click) DrawMove(x, y);
if (interactionType == PrimaryInteractionType.Draw9Grid) Draw9Grid(x, y);
if (interactionType == PrimaryInteractionType.Draw25Grid) Draw25Grid(x, y);
else if (interactionType == PrimaryInteractionType.Draw9Grid) Draw9Grid(x, y);
else if (click == PrimaryInteractionStart.Click) DrawMove(x, y);
}
}
@ -852,12 +854,6 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
Hover(x, y);
}
private void Draw25Grid(double x, double y) {
var map = MapUnderCursor(x, y);
map.Draw25Grid(history.CurrentChange, tilesToDraw, x, y);
Hover(x, y);
}
private void DrawUp(double x, double y) {
rectangleBackup = null;
history.ChangeCompleted();
@ -1171,9 +1167,6 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
if (use9Grid && IsValid9GridSelection) {
DrawMultipleTiles = false;
width = height = 1;
} else if (use25Grid && IsValid25GridSelection) {
DrawMultipleTiles = false;
width = height = 1;
} else {
DrawMultipleTiles = true;
}
@ -1193,9 +1186,10 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
}
private void FillMultiTileRender() {
NotifyPropertiesChanged(nameof(IsValid9GridSelection), nameof(IsValid25GridSelection));
NotifyPropertiesChanged(nameof(IsValid9GridSelection));
if (tilesToDraw == null) return;
var localCopy = tilesToDraw;
if (IsValid9GridSelection && use9Grid) singletons.WorkDispatcher.DispatchWork(() => PrimaryMap.DiscoverCornersFor9Grid(localCopy));
var localRenders = primaryMap.BlockRenders;
var (width, height) = (localCopy.GetLength(0), localCopy.GetLength(1));
var scale = (width < 4 && height < 4) ? 2 : 1;
@ -1697,45 +1691,25 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
private bool selectionFromBlock = false;
private Point blockInteractionStart;
// when in 9-grid mode, show a 2x2 draw area, and draw from blocks in the 9-grid
private bool use9Grid;
public bool Use9Grid { get => use9Grid; set => Set(ref use9Grid, value, old => {
if (use9Grid && IsValid9GridSelection) DrawMultipleTiles = false;
else DrawMultipleTiles = tilesToDraw != null && (tilesToDraw.GetLength(0) > 1 || tilesToDraw.GetLength(1) > 1);
}); }
public bool Use9Grid {
get => use9Grid;
set => Set(ref use9Grid, value, old => {
if (use9Grid && IsValid9GridSelection) {
DrawMultipleTiles = true;
singletons.WorkDispatcher.DispatchWork(() => PrimaryMap.DiscoverCornersFor9Grid(tilesToDraw));
} else {
DrawMultipleTiles = tilesToDraw != null && (tilesToDraw.GetLength(0) > 1 || tilesToDraw.GetLength(1) > 1);
}
});
}
public bool IsValid9GridSelection {
get {
return tilesToDraw != null && tilesToDraw.GetLength(0) == 3 && tilesToDraw.GetLength(1) == 3;
}
}
private bool use25Grid;
public bool Use25Grid { get => use25Grid; set => Set(ref use25Grid, value, old => {
if (use25Grid && IsValid25GridSelection) DrawMultipleTiles = false;
else DrawMultipleTiles = tilesToDraw != null && (tilesToDraw.GetLength(0) > 1 || tilesToDraw.GetLength(1) > 1);
}); }
public bool IsValid25GridSelection {
get {
if (tilesToDraw == null || tilesToDraw.GetLength(0) != 5 || tilesToDraw.GetLength(1) != 5) return false;
// require like-blocks
// 25 blocks - 4 corners - 1 center - 4 partial centers - 4 corners - 4 edges = 8 duplicate blocks
// 4 corner duplicates
if (tilesToDraw[1, 0] != tilesToDraw[0, 1]) return false;
if (tilesToDraw[3, 0] != tilesToDraw[4, 1]) return false;
if (tilesToDraw[0, 3] != tilesToDraw[1, 4]) return false;
if (tilesToDraw[3, 4] != tilesToDraw[4, 3]) return false;
// 4 center duplicates
if (tilesToDraw[2, 2] != tilesToDraw[2, 1]) return false;
if (tilesToDraw[2, 2] != tilesToDraw[2, 3]) return false;
if (tilesToDraw[2, 2] != tilesToDraw[1, 2]) return false;
if (tilesToDraw[2, 2] != tilesToDraw[3, 2]) return false;
return true;
}
}
public void SelectBlock(int x, int y) {
while (y * BlockMapViewModel.BlocksPerRow + x > PrimaryMap.BlockRenders.Count) y -= 1;
blockInteractionStart = new(x, y);
@ -1793,7 +1767,6 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
PrimaryMap.BlockEditor.ShowTiles = false;
FillMultiTileRender();
if (use9Grid && IsValid9GridSelection) DrawMultipleTiles = false;
else if (use25Grid && IsValid25GridSelection) DrawMultipleTiles = false;
else DrawMultipleTiles = true;
BlockEditorVisible = false;
}
@ -2309,7 +2282,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
ControlClick = 4,
ShiftClick = 8,
}
public enum PrimaryInteractionType { None, Draw, Event, RectangleDraw, Draw9Grid, Draw25Grid }
public enum PrimaryInteractionType { None, Draw, Event, RectangleDraw, Draw9Grid }
public record BlocksetCache(ObservableCollection<BlocksetOption> Primary, ObservableCollection<BlocksetOption> Secondary) {
public void CalculateBlocksetOptions(IDataModel model) {

View File

@ -30,6 +30,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
public TextEditorViewModel() {
Keywords.CollectionChanged += (sender, e) => UpdateLayers();
Constants.CollectionChanged += (sender, e) => UpdateLayers();
SyntaxHighlighting = true;
}
public TextEditorViewModel(bool syntaxHighlighting) {

View File

@ -90,10 +90,10 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
public ObservableCollection<GotoMapButton> MapPreviews { get; } = new();
public MapOptionsArrayElementViewModel(IWorkDispatcher dispatcher, MapEditorViewModel mapEditor, string tableName, int index) {
public MapOptionsArrayElementViewModel(IWorkDispatcher dispatcher, IDelayWorkTimer timer, MapEditorViewModel mapEditor, string tableName, int index) {
this.dispatcher = dispatcher;
(this.mapEditor, this.tableName, this.index) = (mapEditor, tableName, index);
dispatcher.RunBackgroundWork(Load);
timer.DelayCall(TimeSpan.FromSeconds(.5), Load);
}
private void Load() {

View File

@ -24,6 +24,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
private readonly Selection selection;
private readonly ChangeHistory<ModelDelta> history;
private readonly IRaiseMessageTab messageTab;
private readonly IDelayWorkTimer recompileTimer;
public event EventHandler<ErrorInfo> ModelDataChanged;
public event EventHandler AttentionNewContent;
@ -98,6 +99,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
public CodeTool(Singletons singletons, ViewPort viewPort, Selection selection, ChangeHistory<ModelDelta> history, IRaiseMessageTab messageTab) {
this.singletons = singletons;
recompileTimer = singletons.WorkDispatcher.CreateDelayTimer();
var gameHash = viewPort.Model.GetShortGameCode();
thumb = new ThumbParser(singletons);
script = new ScriptParser(gameHash, singletons.ScriptLines, 0x02);
@ -323,7 +325,8 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
if (run != null && run.Start != body.Address) run = null;
int length = parser.FindLength(model, body.Address);
using (ModelCacheScope.CreateScope(model)) {
// don't need to run this if they're still typing
recompileTimer.DelayCall(TimeSpan.FromSeconds(.5), () => {
var initialStart = selection.Scroll.ViewPointToDataIndex(selection.SelectionStart);
var initialEnd = selection.Scroll.ViewPointToDataIndex(selection.SelectionEnd);
if (initialStart > initialEnd) (initialStart, initialEnd) = (initialEnd, initialStart);
@ -353,7 +356,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
body.Address = start; // in case of the code getting repointed
}
UpdateContents(start, parser, body.Address, length);
}
});
}
private void UpdateScriptHelpFromLine(object sender, HelpContext context) {

View File

@ -52,7 +52,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
};
}
public class ComboBoxArrayElementViewModel : ViewModelCore, IArrayElementViewModel {
public class ComboBoxArrayElementViewModel : ViewModelCore, IMultiEnabledArrayElementViewModel {
private string name, enumName;
private int start, length;

View File

@ -40,9 +40,10 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
if (selectedIndex < 0 || selectedIndex >= FilteredOptions.Count) return;
interactionType = InteractionType.DropDown;
selectedIndex = value;
dropDownIsOpen = false;
if (!ClearFilter()) {
displayText = AllOptions[selectedIndex].Text;
NotifyPropertiesChanged(nameof(SelectedIndex), nameof(DisplayText), nameof(ModelValue));
NotifyPropertiesChanged(nameof(SelectedIndex), nameof(DisplayText), nameof(DropDownIsOpen), nameof(ModelValue));
}
interactionType = InteractionType.None;
}

View File

@ -2,7 +2,10 @@
using HavenSoft.HexManiac.Core.Models.Runs;
using HavenSoft.HexManiac.Core.ViewModels.DataFormats;
using System;
using System.Globalization;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;
namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
public enum ElementContentViewModelType {
@ -20,7 +23,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
string UpdateViewModelFromModel(FieldArrayElementViewModel viewModel);
}
public class FieldArrayElementViewModel : ViewModelCore, IArrayElementViewModel {
public class FieldArrayElementViewModel : ViewModelCore, IMultiEnabledArrayElementViewModel {
private readonly IFieldArrayElementViewModelStrategy strategy;
private string name;
@ -46,7 +49,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
private string theme; public string Theme { get => theme; set => Set(ref theme, value); }
public bool IsInError => errorText != string.Empty;
string errorText;
private string errorText;
public string ErrorText {
get => errorText;
set {
@ -318,4 +321,103 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
return text;
}
}
public class MultiFieldArrayElementViewModel : ViewModelCore, IArrayElementViewModel {
private string theme, content;
private bool visible = true;
private List<IMultiEnabledArrayElementViewModel> fields = new();
public string Theme { get => theme; set => Set(ref theme, value); }
public bool Visible { get => visible; set => Set(ref visible, value); }
public bool IsInError => false;
public string ErrorText => string.Empty;
public int ZIndex => 0;
public event EventHandler DataChanged;
public event EventHandler DataSelected;
public ICommand Undo { get; }
public ICommand Redo { get; }
public MultiFieldArrayElementViewModel(ViewPort port) => (Undo, Redo) = (port.Undo, port.Redo);
public void Add(IMultiEnabledArrayElementViewModel field) {
fields.Add(field);
RecalculateBody();
}
// consider hiding fields that don't match the filter
public bool Filter(string filter) {
foreach (var element in fields) {
if (element.Name.MatchesPartial(filter)) return true;
}
return false;
}
public string Content {
get => content;
set => Set(ref content, value, oldValue => {
var lines = content.SplitLines();
for (int i = 0; i < fields.Count; i++) {
if (i >= lines.Length) break;
var parts = lines[i].Split(':', 2);
if (parts.Length == 1) continue;
var content = parts[1].Trim();
if (fields[i] is FieldArrayElementViewModel field) {
field.Content = content;
} else if (fields[i] is ComboBoxArrayElementViewModel combo) {
combo.FilteringComboOptions.DisplayText = content;
combo.FilteringComboOptions.SelectConfirm();
}
}
DataChanged?.Invoke(this, EventArgs.Empty);
});
}
public bool TryCopy(IArrayElementViewModel other) {
if (other is not MultiFieldArrayElementViewModel that) return false;
return content == that.content && fields.Count == that.fields.Count && fields.Count.Range().All(i => fields[i].TryCopy(that.fields[i]));
}
private void RecalculateBody() {
var longestName = fields.Select(f => f.Name.Length).Max();
var content = new StringBuilder();
bool first = true;
foreach (var field in fields) {
if (!first) content.AppendLine();
else first = false;
content.Append(field.Name.PadRight(longestName, ' '));
content.Append(" : ");
if (field is FieldArrayElementViewModel field1) {
content.Append(field1.Content);
} else if (field is ComboBoxArrayElementViewModel combo) {
content.Append(combo.FilteringComboOptions.DisplayText);
}
}
this.content = content.ToString();
NotifyPropertyChanged(nameof(Content));
}
public IReadOnlyList<AutocompleteItem> GetAutoComplete(string line, int caretLineIndex, int caretCharacterIndex) {
var parts = line.Split(':', 2);
if (parts.Length != 2) return Array.Empty<AutocompleteItem>();
var name = parts[0].Trim();
var field = fields.FirstOrDefault(f => f.Name == name);
if (field == null) return Array.Empty<AutocompleteItem>();
// right now, the field is always something like free text, free number, or free color
// no auto-complete makes sense
// but this method is here as a stub in case we add combobox content into the MultiField.
if (field is ComboBoxArrayElementViewModel combo) {
var content = parts[1].Trim();
combo.FilteringComboOptions.DisplayText = content;
return combo.FilteringComboOptions.FilteredOptions
.Select(option => new AutocompleteItem(option.Text, parts[0] + ": " + option.Text) { CharacterOffset = parts[0].Length })
.ToArray();
}
return Array.Empty<AutocompleteItem>();
}
}
}

View File

@ -56,6 +56,11 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
bool TryCopy(IArrayElementViewModel other);
}
// shared interface for fields/comboboxes to appear in multi-boxes
public interface IMultiEnabledArrayElementViewModel : IArrayElementViewModel {
string Name { get; set; }
}
public class SplitterArrayElementViewModel : ViewModelCore, IArrayElementViewModel {
event EventHandler IArrayElementViewModel.DataChanged { add { } remove { } }
event EventHandler IArrayElementViewModel.DataSelected { add { } remove { } }
@ -101,6 +106,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
if (child is FieldArrayElementViewModel faevm) childVisible = filterMatchesGroup || faevm.Name.MatchesPartial(filter);
if (child is ComboBoxArrayElementViewModel cbaevm) childVisible = filterMatchesGroup || cbaevm.Name.MatchesPartial(filter);
if (child is MultiFieldArrayElementViewModel multi) childVisible = filterMatchesGroup || multi.Filter(filter);
if (child is CalculatedElementViewModel cevm) childVisible = filterMatchesGroup || cevm.Name.MatchesPartial(filter);
if (child is IStreamArrayElementViewModel saevm) childVisible = lastFieldVisible || (saevm is TextStreamElementViewModel tStream && tStream.Content.MatchesPartial(filter));
if (child is BitListArrayElementViewModel blaevm) {

View File

@ -194,6 +194,9 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
var run = GetRun();
Pages = run.Pages;
UpdateAvailablePalettes(Start);
this.Bind(nameof(Visible), (sender, args) => {
if (Visible && needsUpdate) UpdateTiles(Start, CurrentPage, false);
});
}
private void UpdateAvailablePalettes(int start) {
@ -241,13 +244,18 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
protected override void PageChanged() => UpdateTiles(CurrentPage);
private bool needsUpdate = false;
public void UpdateTiles(int? pageOption = null) {
// TODO support multiple layers
int page = pageOption ?? CurrentPage;
if (GetRun() is LzTilemapRun mapRun) mapRun.FindMatchingTileset(Model);
UpdateTiles(Start, page, false);
if (Visible) {
UpdateTiles(Start, page, false);
} else {
needsUpdate = true;
}
}
protected override bool CanExecuteAddPage() {
@ -266,6 +274,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
private int[,] lastPixels;
private IReadOnlyList<short> lastColors;
private void UpdateTiles(int start, int page, bool exitPaletteSearchEarly) {
needsUpdate = false;
var run = GetRun();
var tableIndex = -1;

View File

@ -36,12 +36,14 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
public bool DisplayHeader => GroupName != DefaultName;
public string GroupName { get => groupName; set => Set(ref groupName, value, old => NotifyPropertyChanged(nameof(DisplayHeader))); }
private readonly ViewPort viewPort;
public ObservableCollection<IArrayElementViewModel> Members { get; } = new();
public Action<IStreamArrayElementViewModel> ForwardModelChanged { get; init; }
public Action<IStreamArrayElementViewModel> ForwardModelDataMoved { get; init; }
public TableGroupViewModel() { GroupName = DefaultName; }
public TableGroupViewModel(ViewPort viewPort) { GroupName = DefaultName; this.viewPort = viewPort; }
public bool IsOpen => isOpen;
@ -51,29 +53,59 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
isOpen = true;
}
public void Add(IArrayElementViewModel child, string theme = null) {
child.Theme = theme;
if (currentMember == Members.Count) {
private bool useMultiFieldFeature = false;
public bool UseMultiFieldFeature { get => useMultiFieldFeature; set => Set(ref useMultiFieldFeature, value); }
private MultiFieldArrayElementViewModel multiInProgress;
public void Add(IArrayElementViewModel child) {
if (UseMultiFieldFeature && child is IMultiEnabledArrayElementViewModel newField) {
if (multiInProgress == null) multiInProgress = new(viewPort);
multiInProgress.Add(newField);
} else if (currentMember == Members.Count) {
if (multiInProgress != null) {
Members.Add(multiInProgress);
multiInProgress = null;
currentMember++;
}
Members.Add(child);
currentMember++;
} else {
// using var scope = Members[currentMember].SilencePropertyNotifications();
if (!Members[currentMember].TryCopy(child)) {
Members[currentMember] = child;
if (multiInProgress != null) {
if (Members[currentMember].TryCopy(multiInProgress)) {
// no need to copy
} else {
Members[currentMember] = multiInProgress;
}
multiInProgress = null;
currentMember++;
Add(child); // we're adding a non-multi, and now have dealt with the current multi. Recurse so the child can be added using either "full" or "replace" strategy
} else if (Members[currentMember].TryCopy(child)) {
currentMember += 1; // copied over successfully
} else {
Members[currentMember].Theme = child.Theme;
// replace existing
Members[currentMember] = child;
currentMember += 1;
}
}
currentMember += 1;
}
public void Close() {
if (!isOpen) return;
if (multiInProgress != null) {
if (currentMember == Members.Count) {
Members.Add(multiInProgress);
} else if (!Members[currentMember].TryCopy(multiInProgress)) {
Members[currentMember] = multiInProgress;
}
currentMember += 1;
multiInProgress = null;
}
while (Members.Count > currentMember) Members.RemoveAt(Members.Count - 1);
isOpen = false;
// Members.RaiseRefresh();
}
public void AddChildrenFromTable(ViewPort viewPort, Selection selection, ITableRun table, int index, string theme, SplitterArrayElementViewModel header, TableGroupViewModel helperGroup, int splitPortion = -1) {
public void AddChildrenFromTable(ViewPort viewPort, Selection selection, ITableRun table, int index, SplitterArrayElementViewModel header, TableGroupViewModel helperGroup, int splitPortion = -1) {
var itemAddress = table.Start + table.ElementLength * index;
var originalItemAddress = itemAddress;
var currentPartition = 0;
@ -126,9 +158,9 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
} else {
throw new NotImplementedException();
}
if (!item.IsUnused()) {
Add(viewModel, theme);
helperGroup.AddChildrenFromPointerSegment(viewPort, theme, itemAddress, item, viewModel, header, recursionLevel: 0);
if (!item.IsUnused() && viewModel is not null) {
Add(viewModel);
helperGroup.AddChildrenFromPointerSegment(viewPort, itemAddress, item, viewModel, header, recursionLevel: 0);
}
itemAddress += item.Length;
}
@ -178,7 +210,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
Add(new ButtonArrayElementViewModel("Edit Map", () => viewPort.Goto.Execute(name)));
}
private void AddChildrenFromPointerSegment(ViewPort viewPort, string theme, int itemAddress, ArrayRunElementSegment item, IArrayElementViewModel parent, SplitterArrayElementViewModel header, int recursionLevel) {
private void AddChildrenFromPointerSegment(ViewPort viewPort, int itemAddress, ArrayRunElementSegment item, IArrayElementViewModel parent, SplitterArrayElementViewModel header, int recursionLevel) {
if (!(item is ArrayRunPointerSegment pointerSegment)) return;
if (pointerSegment.InnerFormat == string.Empty) return;
var destination = viewPort.Model.ReadPointer(itemAddress);
@ -223,7 +255,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
if (!Members[myIndex].TryCopy(newStream)) Members[myIndex] = newStream;
};
ForwardModelDataMoved(streamElement);
Add(streamElement, theme);
Add(streamElement);
if (streamRun is ITableRun tableRun && recursionLevel < 1) {
int segmentOffset = 0;
@ -231,7 +263,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
if (!(tableRun.ElementContent[i] is ArrayRunPointerSegment)) { segmentOffset += tableRun.ElementContent[i].Length; continue; }
for (int j = 0; j < tableRun.ElementCount; j++) {
itemAddress = tableRun.Start + segmentOffset + j * tableRun.ElementLength;
AddChildrenFromPointerSegment(viewPort, theme, itemAddress, tableRun.ElementContent[i], streamElement, header, recursionLevel + 1);
AddChildrenFromPointerSegment(viewPort, itemAddress, tableRun.ElementContent[i], streamElement, header, recursionLevel + 1);
}
segmentOffset += tableRun.ElementContent[i].Length;
}

View File

@ -18,6 +18,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
private readonly ViewPort viewPort;
private readonly IToolTrayViewModel toolTray;
private readonly IWorkDispatcher dispatcher;
private readonly IDelayWorkTimer loadMapUsageTimer, dataChangedTimer;
public string Name => "Table";
@ -129,6 +130,15 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
}
}
private bool useMultiFieldFeature = false;
public bool UseMultiFieldFeature {
get => useMultiFieldFeature;
set => Set(ref useMultiFieldFeature, value, old => {
foreach (var group in Groups) group.UseMultiFieldFeature = useMultiFieldFeature;
DataForCurrentRunChanged();
});
}
public ObservableCollection<IArrayElementViewModel> UsageChildren { get; }
public ObservableCollection<TableGroupViewModel> Groups { get; }
@ -186,8 +196,10 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
this.viewPort = viewPort;
this.toolTray = toolTray;
this.dispatcher = dispatcher;
loadMapUsageTimer = dispatcher.CreateDelayTimer();
dataChangedTimer = /* */ new ImmediateWorkTimer(); //*/ dispatcher.CreateDelayTimer();
CurrentElementSelector = new FilteringComboOptions();
CurrentElementSelector.Bind(nameof(FilteringComboOptions.SelectedIndex), UpdateViewPortSelectionFromTableComboBoxIndex);
CurrentElementSelector.Bind(nameof(FilteringComboOptions.ModelValue), UpdateViewPortSelectionFromTableComboBoxIndex);
Groups = new();
UsageChildren = new();
@ -283,29 +295,17 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
public IList<IArrayElementViewModel> Children => Groups.SelectMany(group => group.Members).ToList();
private void AddGroup() {
Groups.Add(new TableGroupViewModel {
Groups.Add(new TableGroupViewModel(viewPort) {
ForwardModelChanged = element => element.DataChanged += ForwardModelChanged,
ForwardModelDataMoved = element => element.DataMoved += ForwardModelDataMoved,
});
}
private readonly string[] themes = new string[] {
//nameof(Theme.Text1),
//nameof(Theme.Text2),
//nameof(Theme.Data1),
//nameof(Theme.Data2),
//nameof(Theme.Accent),
//nameof(Theme.Stream1),
//nameof(Theme.Stream2),
nameof(Theme.Background),
};
private int childIndexGroup = 0, themeIndex = 0;
private int childIndexGroup = 0;
private void AddChild(IArrayElementViewModel child) {
if (child == null) return;
if (child is SplitterArrayElementViewModel) themeIndex = (themeIndex + 1) % themes.Length;
while (Groups.Count <= childIndexGroup) AddGroup();
Groups[childIndexGroup].Add(child, themes[themeIndex]);
Groups[childIndexGroup].Add(child);
}
private void MoveToNextGroup() {
@ -337,13 +337,24 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
private bool dataForCurrentRunChangeUpdate;
public void DataForCurrentRunChanged() {
// ignore callbacks while any held comboboxes are open
foreach (var group in Groups) {
foreach (var member in group.Members) {
if (member is ComboBoxArrayElementViewModel box && box.FilteringComboOptions.DropDownIsOpen) return;
}
}
// must be longer than initial key-hold delay or app will studder
dataChangedTimer.DelayCall(TimeSpan.FromSeconds(.6), DataForCurrentRunChangedCore);
}
private void DataForCurrentRunChangedCore() {
foreach (var group in Groups) {
foreach (var member in group.Members) ClearHandlers(member);
}
foreach (var child in UsageChildren) ClearHandlers(child);
foreach (var group in Groups) group.Open();
childIndexGroup = 0;
themeIndex = 0;
usageChildInsertionIndex = 0;
var array = model.GetNextRun(Address) as ITableRun;
@ -389,7 +400,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
streamGroup = Groups[1];
streamGroup.Open();
} else {
streamGroup = new TableGroupViewModel {
streamGroup = new TableGroupViewModel(viewPort) {
ForwardModelChanged = element => element.DataChanged += ForwardModelChanged,
ForwardModelDataMoved = element => element.DataMoved += ForwardModelDataMoved,
};
@ -398,7 +409,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
var header = new SplitterArrayElementViewModel(viewPort, basename, elementOffset);
AddChild(header);
Groups[childIndexGroup].AddChildrenFromTable(viewPort, selection, array, index, themes[themeIndex], header, streamGroup);
Groups[childIndexGroup].AddChildrenFromTable(viewPort, selection, array, index, header, streamGroup);
MoveToNextGroup();
Groups[0].GroupName = basename;
} else {
@ -414,7 +425,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
streamGroup.GroupName = TableGroupViewModel.DefaultName;
streamGroup.Open();
} else {
streamGroup = new TableGroupViewModel {
streamGroup = new TableGroupViewModel(viewPort) {
ForwardModelChanged = element => element.DataChanged += ForwardModelChanged,
ForwardModelDataMoved = element => element.DataMoved += ForwardModelDataMoved,
};
@ -439,7 +450,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
elementOffset = currentArray.Start + currentArray.ElementLength * currentIndex;
var header = new SplitterArrayElementViewModel(viewPort, tableName, elementOffset);
AddChild(header);
Groups[childIndexGroup].AddChildrenFromTable(viewPort, selection, currentArray, currentIndex, themes[themeIndex], header, helperGroup, partition);
Groups[childIndexGroup].AddChildrenFromTable(viewPort, selection, currentArray, currentIndex, header, helperGroup, partition);
}
}
while (Groups.Count <= childIndexGroup) AddGroup();
@ -536,6 +547,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
private void UpdateViewPortSelectionFromTableComboBoxIndex(object sender = null, EventArgs e = null) {
if (selfChange) return;
if (CurrentElementSelector.DropDownIsOpen) return;
var array = (ITableRun)model.GetNextRun(Address);
var address = array.Start + array.ElementLength * CurrentElementSelector.SelectedIndex;
selection.SelectionStart = selection.Scroll.DataIndexToViewPoint(address);
@ -624,7 +636,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
// maps
if (viewPort.MapEditor != null && viewPort.MapEditor.IsValidState && !viewPort.SpartanMode) {
var mapOptions = new MapOptionsArrayElementViewModel(dispatcher, viewPort.MapEditor, basename, index);
var mapOptions = new MapOptionsArrayElementViewModel(dispatcher, loadMapUsageTimer, viewPort.MapEditor, basename, index);
mapOptions.MapPreviews.CollectionChanged += (sender, e) => NotifyPropertyChanged(nameof(HasUsageOptions));
AddUsageChild(mapOptions); // always add, but invisible when empty
}

View File

@ -3314,7 +3314,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
}
if (run is ITableRun && sender != Tools.StringTool && Model.GetNextRun(Tools.StringTool.Address).Start == run.Start) Tools.StringTool.DataForCurrentRunChanged();
if (run is ITableRun && Model.GetNextRun(Tools.TableTool.Address).Start == run.Start) Tools.TableTool.DataForCurrentRunChanged();
if (run is ITableRun && Model.GetNextRun(Tools.TableTool.Address).Start == run.Start) Tools.TableTool.DataForCurrentRunChanged();
}
private void ModelDataMovedByTool(object sender, (int originalLocation, int newLocation) locations) {

View File

@ -851,7 +851,7 @@ namespace HavenSoft.HexManiac.Tests {
public void TableTool_FilterTupleFieldName_TupleVisible() {
var tool = ViewPort.Tools.TableTool;
tool.Groups.Clear();
tool.Groups.Add(new());
tool.Groups.Add(new(ViewPort));
var section = new SplitterArrayElementViewModel(ViewPort, "section", 0);
var tuple = new TupleArrayElementViewModel(ViewPort, new ArrayRunTupleSegment("tuples", "|abc:|def:|ijk:|xyz:", 4), 0);

View File

@ -340,9 +340,9 @@ namespace HavenSoft.HexManiac.Tests {
var tokenLines = lines.Select(line => line.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries));
lines = tokenLines.Select(tokens => " ".Join(tokens)).ToArray();
Assert.Equal("index: (0)", lines[0]);
Assert.Equal("unknown: 0x0000", lines[1]);
Assert.Equal("unused: 0x0000", lines[2]);
Assert.Equal("index : (0)", lines[0]);
Assert.Equal("unknown : 0x0000", lines[1]);
Assert.Equal("unused : 0x0000", lines[2]);
}
[Fact]

View File

@ -6,10 +6,12 @@ using HavenSoft.HexManiac.Core.ViewModels.Tools;
using HavenSoft.HexManiac.WPF.Resources;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace HavenSoft.HexManiac.WPF.Controls {
public partial class AutocompleteOverlay {
@ -92,6 +94,8 @@ namespace HavenSoft.HexManiac.WPF.Controls {
} else {
throw new NotImplementedException();
}
} else if (DataContext is MultiFieldArrayElementViewModel multi) {
getAutocomplete = multi.GetAutoComplete;
} else if (DataContext is TrainerTeamViewModel team) {
getAutocomplete = team.GetTrainerAutocomplete;
} else if (DataContext is CodeBody body) {
@ -113,9 +117,13 @@ namespace HavenSoft.HexManiac.WPF.Controls {
var verticalOffset = TargetTextBox.VerticalOffset;
var lineHeight = TargetTextBox.ExtentHeight / totalLines;
var verticalStart = lineHeight * editLineIndex - verticalOffset + 2;
double horizontalStart = 0;
var options = getAutocomplete(lines[lineIndex], lineIndex, index);
if (options != null && options.Count > 0) {
var offset = options[0].CharacterOffset;
var typeFace = new Typeface(TargetTextBox.FontFamily, TargetTextBox.FontStyle, TargetTextBox.FontWeight, TargetTextBox.FontStretch);
horizontalStart = new FormattedText(new string('_', offset), CultureInfo.InvariantCulture, FlowDirection.LeftToRight, typeFace, Target.FontSize, null, 96).Width;
AutocompleteItems.ItemsSource = AutoCompleteSelectionItem.Generate(options, 0).ToList();
ShowAutocompleteOptions();
} else if (options != null) {
@ -124,7 +132,9 @@ namespace HavenSoft.HexManiac.WPF.Controls {
var screenVertical = TargetTextBox.TranslatePoint(new Point(0, verticalStart), Application.Current.MainWindow).Y;
ScrollBorder.UpdateLayout();
if (Application.Current.MainWindow.ActualHeight - screenVertical < 200) verticalStart -= ScrollBorder.ActualHeight + 12;
AutocompleteTransform.X = horizontalStart;
AutocompleteTransform.Y = verticalStart;
Popup.Reposition();
ignoreNextSelectionChange = true;

View File

@ -0,0 +1,34 @@
using HavenSoft.HexManiac.Core;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace HavenSoft.HexManiac.WPF.Controls;
public class DelayWorkTimer : IDelayWorkTimer {
private CancellationTokenSource? cancel;
public bool HasScheduledWork => cancel != null;
public DelayWorkResult DelayCall(TimeSpan delay, Action action) {
var result = HasScheduledWork ? DelayWorkResult.WorkScheduledAndPreviousWorkCleared : DelayWorkResult.WorkScheduled;
cancel?.Cancel();
cancel = new CancellationTokenSource();
Run(delay, action, cancel.Token);
return result;
}
public void Reset() {
cancel?.Cancel();
cancel = null;
}
private async void Run(TimeSpan delay, Action action, CancellationToken token) {
try {
await Task.Delay(delay, token);
} catch (TaskCanceledException) { }
if (token.IsCancellationRequested) return;
action();
cancel = null;
}
}

View File

@ -130,9 +130,6 @@
<DataTrigger Binding="{Binding IsValid9GridSelection}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding IsValid25GridSelection}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Decorator.Style>
@ -151,20 +148,6 @@
</ToolTip>
</CheckBox.ToolTip>
</CheckBox>
<CheckBox IsChecked="{Binding Use25Grid}" Content="Draw 25-Grid" VerticalAlignment="Top" Grid.Row="1" HorizontalAlignment="Center"
Visibility="{Binding IsValid25GridSelection, Converter={StaticResource BoolToVisibility}}">
<CheckBox.ToolTip>
<ToolTip>
<TextBlock>
Draw one of the selected 25 blocks based on a grid.
<LineBreak />
This can make drawing multi-block details like paths,
<LineBreak />
water, or mountains much faster.
</TextBlock>
</ToolTip>
</CheckBox.ToolTip>
</CheckBox>
<TextBlock Foreground="{DynamicResource Secondary}" VerticalAlignment="Center" HorizontalAlignment="Center" TextAlignment="Center">
<TextBlock.Style>
<Style TargetType="TextBlock" BasedOn="{StaticResource {x:Type TextBlock}}">
@ -175,7 +158,6 @@
<Condition Binding="{Binding BlockEditorVisible}" Value="False" />
<Condition Binding="{Binding DrawMultipleTiles}" Value="False" />
<Condition Binding="{Binding IsValid9GridSelection}" Value="False" />
<Condition Binding="{Binding IsValid25GridSelection}" Value="False" />
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Visibility" Value="Visible" />

View File

@ -268,6 +268,7 @@
</Grid>
<!-- Add X New -->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,4">
<CheckBox IsChecked="{Binding UseMultiFieldFeature}" Margin="0,0,10,0" Content="Group Similar Fields" />
<Border ToolTipService.InitialShowDelay="0">
<Border.ToolTip>
<ToolTip Background="{DynamicResource Backlight}" BorderBrush="{DynamicResource Accent}" BorderThickness="1">
@ -325,11 +326,6 @@
<Grid Background="{DynamicResource Backlight}" Visibility="{Binding DisplayHeader, Converter={StaticResource BoolToVisibility}}">
<TextBlock HorizontalAlignment="Center" Text="{Binding GroupName}" FontSize="20" Height="26"/>
</Grid>
<!-- - ->
<hsg3hv:TableGroupPanel Source="{Binding Members}" />
<!- - -->
<!-- Let's try replacing this entire ItemsControl with a new way to display the Members and see if that's faster... -->
<!-- -->
<ItemsControl ItemsSource="{Binding Members}">
<ItemsControl.ItemContainerStyle>
<Style>
@ -393,7 +389,6 @@
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Rectangle Width="1" Margin="-2,-2,0,-2" HorizontalAlignment="Left" Fill="{Binding Theme, Converter={StaticResource Theme}}" />
<hsg3hv:AngleBorder Grid.Column="1" Direction="Out">
<TextBlock Text="{Binding Name}" />
</hsg3hv:AngleBorder>
@ -419,7 +414,6 @@
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Rectangle HorizontalAlignment="Left" Width="1" Fill="{Binding Theme, Converter={StaticResource Theme}}" />
<hsg3hv:AngleBorder Grid.Column="1" Direction="Left">
<TextBlock Text="{Binding Name}"/>
</hsg3hv:AngleBorder>
@ -438,7 +432,6 @@
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Rectangle HorizontalAlignment="Left" Width="1" Margin="-2,-2,0,-2" Fill="{Binding Theme, Converter={StaticResource Theme}}" />
<hsg3hv:AngleBorder Grid.Column="1" Direction="Left">
<TextBlock Text="{Binding Name}" />
</hsg3hv:AngleBorder>
@ -471,7 +464,6 @@
</DataTemplate>
<DataTemplate DataType="{x:Type hsg3hvmtr:BitListArrayElementViewModel}">
<Grid HorizontalAlignment="Stretch" Visibility="{Binding Visible, Converter={StaticResource BoolToVisibility}}">
<Rectangle HorizontalAlignment="Left" Width="1" Fill="{Binding Theme, Converter={StaticResource Theme}}" />
<StackPanel>
<Grid HorizontalAlignment="Stretch" Margin="2">
<Grid.ColumnDefinitions>
@ -529,29 +521,27 @@
</DataTemplate>
<DataTemplate DataType="{x:Type hsg3hvmtr:StreamElementViewModel}">
<Grid HorizontalAlignment="Stretch" Visibility="{Binding Visible, Converter={StaticResource BoolToVisibility}}">
<Rectangle Width="1" HorizontalAlignment="Left" Fill="{Binding Theme, Converter={StaticResource Theme}}" />
<StackPanel ToolTip="{Binding ParentName}">
<hsg3hv:CommonTableStreamControl />
<Grid Margin="20,2,2,2" Background="{DynamicResource Backlight}">
<TextBox Name="StreamTextBox" UndoLimit="0" Text="{Binding Content, UpdateSourceTrigger=PropertyChanged}"
Background="Transparent"
VerticalAlignment="Top"
CaretBrush="{DynamicResource Secondary}"
AcceptsReturn="True" FontFamily="Consolas"
Visibility="{Binding ShowContent, Converter={StaticResource BoolToVisibility}}">
<TextBox.InputBindings>
<KeyBinding Modifiers="Ctrl" Key="Z" Command="{Binding Undo}"/>
<KeyBinding Modifiers="Ctrl" Key="Y" Command="{Binding Redo}"/>
</TextBox.InputBindings>
</TextBox>
<hsg3hv:AutocompleteOverlay Target="{Binding ElementName=StreamTextBox}"/>
</Grid>
</StackPanel>
<StackPanel ToolTip="{Binding ParentName}">
<hsg3hv:CommonTableStreamControl />
<Grid Margin="20,2,2,2" Background="{DynamicResource Backlight}">
<TextBox Name="StreamTextBox" UndoLimit="0" Text="{Binding Content, UpdateSourceTrigger=PropertyChanged}"
Background="Transparent"
VerticalAlignment="Top"
CaretBrush="{DynamicResource Secondary}"
AcceptsReturn="True" FontFamily="Consolas"
Visibility="{Binding ShowContent, Converter={StaticResource BoolToVisibility}}">
<TextBox.InputBindings>
<KeyBinding Modifiers="Ctrl" Key="Z" Command="{Binding Undo}"/>
<KeyBinding Modifiers="Ctrl" Key="Y" Command="{Binding Redo}"/>
</TextBox.InputBindings>
</TextBox>
<hsg3hv:AutocompleteOverlay Target="{Binding ElementName=StreamTextBox}"/>
</Grid>
</StackPanel>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type hsg3hvmtr:SpriteElementViewModel}">
<Grid Visibility="{Binding Visible, Converter={StaticResource BoolToVisibility}}">
<Rectangle Width="1" HorizontalAlignment="Left" Fill="{Binding Theme, Converter={StaticResource Theme}}" />
<StackPanel>
<hsg3hv:CommonTableStreamControl />
<hsg3hv:AngleBorder Content="Palette Selection" FontSize="10" Direction="Out" Visibility="{Binding HasMultiplePalettes, Converter={StaticResource BoolToVisibility}}" HorizontalAlignment="Center" />
@ -601,29 +591,24 @@
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type hsg3hvmtr:SpriteIndicatorElementViewModel}">
<Grid HorizontalAlignment="Stretch" Visibility="{Binding Visible, Converter={StaticResource BoolToVisibility}}">
<Rectangle Width="1" HorizontalAlignment="Left" Fill="{Binding Theme, Converter={StaticResource Theme}}" />
<hsg3hv:PixelImage SnapsToDevicePixels="True" DataContext="{Binding Image}" />
</Grid>
<hsg3hv:PixelImage SnapsToDevicePixels="True" DataContext="{Binding Image}" Visibility="{Binding Visible, Converter={StaticResource BoolToVisibility}}" />
</DataTemplate>
<DataTemplate DataType="{x:Type hsg3hvmtr:PaletteElementViewModel}">
<Grid HorizontalAlignment="Stretch" Visibility="{Binding Visible, Converter={StaticResource BoolToVisibility}}">
<Rectangle Width="1" HorizontalAlignment="Left" Fill="{Binding Theme, Converter={StaticResource Theme}}" />
<StackPanel PreviewMouseDown="ActivatePalette">
<hsg3hv:CommonTableStreamControl />
<hsg3hv:PaletteControl DataContext="{Binding Colors}" />
<DockPanel Visibility="{Binding ShowPageControls, Converter={StaticResource BoolToVisibility}}" LastChildFill="False">
<hsg3hv:AngleButton MinWidth="20" Content="-" Direction="Left" Command="{Binding DeletePage}" HorizontalAlignment="Center" ToolTip="Delete the current page of this sprite and palette" />
<hsg3hv:AngleButton MinWidth="20" Content="+" Direction="Right" Command="{Binding AddPage}" DockPanel.Dock="Right" ToolTip="Add a new page to this sprite and palette" />
<hsg3hv:AngleButton MinWidth="60" Content="Previous" Direction="Left" DockPanel.Dock="Left" Click="PageMovePrevious" IsEnabled="{Binding CanMovePrevious}" />
<hsg3hv:AngleButton MinWidth="60" Content="Next" Direction="Right" DockPanel.Dock="Right" Click="PageMoveNext" IsEnabled="{Binding CanMoveNext}" />
</DockPanel>
</StackPanel>
<StackPanel PreviewMouseDown="ActivatePalette">
<hsg3hv:CommonTableStreamControl />
<hsg3hv:PaletteControl DataContext="{Binding Colors}" />
<DockPanel Visibility="{Binding ShowPageControls, Converter={StaticResource BoolToVisibility}}" LastChildFill="False">
<hsg3hv:AngleButton MinWidth="20" Content="-" Direction="Left" Command="{Binding DeletePage}" HorizontalAlignment="Center" ToolTip="Delete the current page of this sprite and palette" />
<hsg3hv:AngleButton MinWidth="20" Content="+" Direction="Right" Command="{Binding AddPage}" DockPanel.Dock="Right" ToolTip="Add a new page to this sprite and palette" />
<hsg3hv:AngleButton MinWidth="60" Content="Previous" Direction="Left" DockPanel.Dock="Left" Click="PageMovePrevious" IsEnabled="{Binding CanMovePrevious}" />
<hsg3hv:AngleButton MinWidth="60" Content="Next" Direction="Right" DockPanel.Dock="Right" Click="PageMoveNext" IsEnabled="{Binding CanMoveNext}" />
</DockPanel>
</StackPanel>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type hsg3hvmtr:TrainerPokemonTeamElementViewModel}">
<Grid HorizontalAlignment="Stretch" Visibility="{Binding Visible, Converter={StaticResource BoolToVisibility}}">
<Rectangle Width="1" HorizontalAlignment="Left" Fill="{Binding Theme, Converter={StaticResource Theme}}" />
<StackPanel ToolTip="{Binding ParentName}">
<hsg3hv:CommonTableStreamControl />
<Grid Margin="20,2,2,2" Background="{DynamicResource Backlight}">
@ -672,7 +657,6 @@
</DataTemplate>
<DataTemplate DataType="{x:Type hsg3hvmtr:TupleArrayElementViewModel}">
<Grid Visibility="{Binding Visible, Converter={StaticResource BoolToVisibility}}">
<Rectangle Width="1" HorizontalAlignment="Left" Fill="{Binding Theme, Converter={StaticResource Theme}}" />
<StackPanel>
<TextBlock Text="{Binding Name}" Margin="5,0"/>
<ItemsControl ItemsSource="{Binding Children}">
@ -716,7 +700,6 @@
</DataTemplate>
<DataTemplate DataType="{x:Type hsg3hvmtr:CalculatedElementViewModel}">
<DockPanel HorizontalAlignment="Stretch" Visibility="{Binding Visible, Converter={StaticResource BoolToVisibility}}">
<Rectangle Width="1" DockPanel.Dock="Left" Fill="{Binding Theme, Converter={StaticResource Theme}}" />
<Grid Margin="1,2,2,2" DockPanel.Dock="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="LeftColumn"/>
@ -755,18 +738,31 @@
</DockPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type hsg3hvmtr:OffsetRenderViewModel}">
<Grid HorizontalAlignment="Stretch" Visibility="{Binding Visible, Converter={StaticResource BoolToVisibility}}">
<Rectangle Width="1" HorizontalAlignment="Left" Fill="{Binding Theme, Converter={StaticResource Theme}}" />
<hsg3hv:PixelImage SnapsToDevicePixels="True" Margin="1,0,0,0"
MouseDown="OffsetRenderMouseDown" MouseMove="OffsetRenderMouseMove" MouseUp="OffsetRenderMouseUp" />
</Grid>
<hsg3hv:PixelImage SnapsToDevicePixels="True" Margin="1,0,0,0" Visibility="{Binding Visible, Converter={StaticResource BoolToVisibility}}"
MouseDown="OffsetRenderMouseDown" MouseMove="OffsetRenderMouseMove" MouseUp="OffsetRenderMouseUp" />
</DataTemplate>
<DataTemplate DataType="{x:Type hsg3hvmtr:PythonButtonElementViewModel}">
<hsg3hv:AngleButton Direction="Out" Content="{Binding Name}" Margin="4" Command="{hsv:MethodCommand Execute}" ToolTip="{Binding Tooltip}" />
</DataTemplate>
<DataTemplate DataType="{x:Type hsg3hvmtr:MultiFieldArrayElementViewModel}">
<StackPanel Visibility="{Binding Visible, Converter={StaticResource BoolToVisibility}}">
<Grid Margin="20,2,2,2" Background="{DynamicResource Backlight}">
<TextBox Name="FieldsTextBox" UndoLimit="0" Text="{Binding Content, UpdateSourceTrigger=PropertyChanged}"
Background="Transparent"
VerticalAlignment="Top"
CaretBrush="{DynamicResource Secondary}"
AcceptsReturn="True" FontFamily="Consolas">
<TextBox.InputBindings>
<KeyBinding Modifiers="Ctrl" Key="Z" Command="{Binding Undo}"/>
<KeyBinding Modifiers="Ctrl" Key="Y" Command="{Binding Redo}"/>
</TextBox.InputBindings>
</TextBox>
<hsg3hv:AutocompleteOverlay Target="{Binding ElementName=FieldsTextBox}"/>
</Grid>
</StackPanel>
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
<!-- -->
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>

View File

@ -14,11 +14,32 @@ using System.Collections.Generic;
using System.Windows.Input;
using System.Linq;
using HavenSoft.HexManiac.WPF.Resources;
using System.Windows.Threading;
using System.Windows.Controls;
namespace HavenSoft.HexManiac.WPF.Controls;
// TODO jump button for enums
// TODO jump button for bit arrays
// TODO jump button behavior
// TODO text selection for textboxes
// TODO text editing for textboxes
// TODO application commands (cut/copy/paste/selectall) for textboxes
// TODO keyboard shortcuts cut/copy/paste/selectall for textboxes
// TODO home/end/left/right/increment/decrement for textboxes
// TODO text editing for enums
// TODO showing filtered dropdown for enums
// additional controls:
// tuples
// calculated fields
namespace HavenSoft.HexManiac.WPF.Controls;
public partial class TableGroupPanel : FrameworkElement {
private readonly SpriteCache spriteCache = new();
private readonly DispatcherTimer timer;
private bool isCursorShowing;
private IArrayElementViewModel keyboardFocusElement, mouseHoverElement;
@ -87,6 +108,21 @@ public partial class TableGroupPanel : FrameworkElement {
#endregion
private Point cursorPosition = new(double.NaN, double.NaN);
public Point CursorPosition {
get => cursorPosition;
set {
cursorPosition = value;
if (double.IsNaN(cursorPosition.X)) {
timer.Stop();
InvalidateVisual();
} else {
isCursorShowing = true;
timer.Start();
}
}
}
public int FontSize { get; private set; } = 16; // TODO dependency property?
public TableGroupPanel() {
@ -94,14 +130,16 @@ public partial class TableGroupPanel : FrameworkElement {
VerticalAlignment = VerticalAlignment.Top;
ClipToBounds = true;
Focusable = true;
timer = new DispatcherTimer(TimeSpan.FromSeconds(.6), DispatcherPriority.ApplicationIdle, BlinkCursor, Dispatcher);
}
protected override void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e) {
base.OnLostKeyboardFocus(e);
if ((keyboardFocusElement != null)) {
if (keyboardFocusElement != null) {
keyboardFocusElement = null;
InvalidateVisual();
}
CursorPosition = new(double.NaN, double.NaN);
}
protected override void OnRender(DrawingContext dc) {
@ -116,6 +154,11 @@ public partial class TableGroupPanel : FrameworkElement {
controls[element].Render(context);
offset += controls[element].Height;
}
if (isCursorShowing && !double.IsNaN(CursorPosition.X)) {
dc.DrawLine(new Pen(Brush(nameof(Theme.Secondary)), 1), CursorPosition, new(cursorPosition.X, cursorPosition.Y + FontSize));
}
}
protected override Size MeasureOverride(Size availableSize) {
@ -243,6 +286,12 @@ public partial class TableGroupPanel : FrameworkElement {
return control;
}
private void BlinkCursor(object sender, EventArgs e) {
isCursorShowing = !isCursorShowing;
InvalidateVisual();
}
/*
#region Helpers
@ -733,11 +782,13 @@ public record RenderContext(DrawingContext Api) {
}
}
public void DrawTextButton(Rect rect, double fontSize, string text, bool isHover) {
public void DrawTextButton(Rect rect, double fontSize, string text, bool isHover, bool isEnabled = true) {
var border = isHover ? nameof(Theme.Primary) : nameof(Theme.Secondary);
var fill = nameof(Theme.Backlight);
var textFill = nameof(Theme.Primary);
if (!isEnabled) { fill = nameof(Theme.Background); border = nameof(Theme.Secondary); textFill = nameof(Theme.Secondary); }
Api.DrawRectangle(Brush(fill), new Pen(Brush(border), 1), rect);
var formattedText = FormattedText(text, fontSize, nameof(Theme.Primary));
var formattedText = FormattedText(text, fontSize, textFill);
if (formattedText.Width > rect.Width - 2) {
fontSize *= (rect.Width - 2) / formattedText.Width;
formattedText = FormattedText(text, fontSize, nameof(Theme.Primary));
@ -745,6 +796,24 @@ public record RenderContext(DrawingContext Api) {
Api.DrawText(formattedText, new(rect.X + rect.Width / 2 - formattedText.Width / 2, rect.Y));
}
/// <summary>
/// Draws a squarish jump button based on the fontSize.
/// </summary>
public void DrawJumpButton(Point start, bool enabled, bool hover) {
int width = CurrentFontSize, height = CurrentFontSize;
Api.PushTransform(new TranslateTransform(start.X, start.Y));
var background = enabled ? nameof(Theme.Backlight) : nameof(Theme.Background);
var border = (hover && enabled) ? nameof(Theme.Primary) : nameof(Theme.Secondary);
var content = $"M0,0 L {width-4},0 {width},{height/2} {width-4},{height} 0,{height} 4,{height/2} Z";
Api.DrawGeometry(Brush(background), new Pen(Brush(border), 1), Geometry.Parse(content));
content = $"M4,3 L {width-6},3 {width-4},{height/2} {width-6},{height-3} 4,{height-3} 6,{height/2} Z";
Api.DrawGeometry(Brush(border), null, Geometry.Parse(content));
Api.Pop();
}
public static FormattedText FormattedText(string text, double size, string foreground)
=> new FormattedText(text, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, Consolas, size, Brush(foreground), 96);
@ -811,37 +880,88 @@ public record GroupDefaultControl(IArrayElementViewModel Element) : GroupFixedHe
}
public record GroupTextControl(FieldArrayElementViewModel Element) : GroupFixedHeighteControl(), IGroupControl {
enum ControlSegment { None, Label, TextBox, Button }
private ControlSegment mouseOver;
public bool IsFocused { get; private set; }
public void MouseEnter(TableGroupPanel parent, MouseEventArgs e) { }
public void MouseDown(TableGroupPanel parent, MouseButtonEventArgs e) {
var text = RenderContext.FormattedText(Element.Content, parent.FontSize, null);
var labelAndContentWidth = Width - parent.FontSize - 2;
var leftEdge = labelAndContentWidth - text.Width - 2;
var characterWidth = text.Width / Element.Content.Length;
var p = e.GetPosition(parent);
var index = (p.X - leftEdge) / characterWidth + .5;
index = (int)index.LimitToRange(0, Element.Content.Length);
parent.CursorPosition = new((int)(leftEdge + index * characterWidth), YOffset + 1);
}
public void MouseMove(TableGroupPanel parent, MouseEventArgs e) {
var p = e.GetPosition(parent);
parent.Cursor = p.X > Width / 2 ? Cursors.IBeam : Cursors.Arrow;
if (e.LeftButton == MouseButtonState.Pressed) {
// TODO mouse drag
} else {
// mouse hover
var newHover = CalculateMouseOver(parent, new(p.X, p.Y - YOffset));
if ((newHover == ControlSegment.Button) != (mouseOver == ControlSegment.Button)) {
mouseOver = newHover;
parent.InvalidateVisual(); // redraw for border
}
}
}
public void MouseUp(TableGroupPanel parent, MouseButtonEventArgs e) {
// TODO end of mouse drag
// TODO need the ability to actually respond to these application commands
if (e.ChangedButton == MouseButton.Right) {
parent.ContextMenu = new ContextMenu {
Items = {
new MenuItem { Command = ApplicationCommands.Cut },
new MenuItem { Command = ApplicationCommands.Copy },
new MenuItem { Command = ApplicationCommands.Paste },
new Separator(),
new MenuItem { Command = ApplicationCommands.SelectAll },
},
};
}
}
public void MouseExit(TableGroupPanel parent, MouseEventArgs e) {
parent.Cursor = Cursors.Arrow;
if (mouseOver == ControlSegment.Button) {
mouseOver = ControlSegment.None;
parent.InvalidateVisual();
}
}
private ControlSegment CalculateMouseOver(TableGroupPanel parent, Point internalPoint) {
var labelAndContentWidth = Width - parent.FontSize;
if (internalPoint.X < labelAndContentWidth / 2) return ControlSegment.Label;
if (internalPoint.X > labelAndContentWidth) return ControlSegment.Button;
return ControlSegment.TextBox;
}
public void Render(RenderContext context) {
var topOfText = YOffset;
var labelAndContentWidth = Width - context.CurrentFontSize - 2;
// label
context.DrawText(new(2, topOfText + 2), context.CurrentFontSize - 4, Width / 2, Element.Name, Primary);
context.DrawText(new(2, topOfText + 2), context.CurrentFontSize - 4, labelAndContentWidth / 2, Element.Name, Primary);
// box
var pen = IsFocused ? context.AccentPen : null;
var background = RenderContext.Brush(Backlight);
context.Api.DrawRectangle(background, pen, new Rect(Width / 2, YOffset + 1, Width / 2, Height - 2));
context.Api.DrawRectangle(background, pen, new Rect(labelAndContentWidth / 2, YOffset + 1, labelAndContentWidth / 2, Height - 2));
// content
var text = RenderContext.FormattedText(Element.Content, context.CurrentFontSize, Primary);
var textWidth = text.Width + 2;
if (textWidth > Width / 2) textWidth = Width / 2; // TODO crop the text if this happens
context.Api.DrawText(text, new(Width - textWidth, topOfText));
if (textWidth > labelAndContentWidth / 2) textWidth = labelAndContentWidth / 2; // TODO crop the text if this happens
context.Api.DrawText(text, new(labelAndContentWidth - textWidth, topOfText));
// TODO render the button
}
public void MouseEnter(TableGroupPanel parent, MouseEventArgs e) { }
public void MouseDown(TableGroupPanel parent, MouseButtonEventArgs e) { }
public void MouseMove(TableGroupPanel parent, MouseEventArgs e) {
var p = e.GetPosition(parent);
parent.Cursor = p.X > Width / 2 ? Cursors.IBeam : Cursors.Arrow;
}
public void MouseUp(TableGroupPanel parent, MouseButtonEventArgs e) { }
public void MouseExit(TableGroupPanel parent, MouseEventArgs e) {
parent.Cursor = Cursors.Arrow;
// goto button
context.DrawJumpButton(new(Width - context.CurrentFontSize, topOfText + 2), Element.CanAccept(), mouseOver == ControlSegment.Button);
}
public void KeyInput(TableGroupPanel parent, KeyEventArgs e) { }
@ -1043,7 +1163,7 @@ public record GroupPaletteControl(PaletteElementViewModel Element) : GroupFixedH
public void TextInput(TableGroupPanel parent, TextCompositionEventArgs e) { }
private int GetCell(TableGroupPanel parent, double x, double y) {
var unitWidth = parent.FontSize / 2 + 4;
var unitWidth = parent.FontSize * 2 / 3 + 4;
var cellY = (int)((y - YOffset) / unitWidth).LimitToRange(0, Element.Colors.ColorHeight);
var cellX = (int)(x / unitWidth).LimitToRange(0,Element.Colors.ColorWidth);
return cellY * Element.Colors.ColorWidth + cellX;
@ -1071,7 +1191,14 @@ public record GroupBitArrayControl(BitListArrayElementViewModel Element) : Group
hover = newHover;
}
public void MouseUp(TableGroupPanel parent, MouseButtonEventArgs e) {
if (GetBit(e.GetPosition(parent)) == mouseClickElement && mouseClickElement != null) {
if (e.ChangedButton == MouseButton.Right) {
parent.ContextMenu = new ContextMenu {
Items = {
new MenuItem { Header = "Select All", Command = Element.SelectAll },
new MenuItem { Header = "Unselect All", Command = Element.UnselectAll },
},
};
} else if (GetBit(e.GetPosition(parent)) == mouseClickElement && mouseClickElement != null) {
mouseClickElement.IsChecked = !mouseClickElement.IsChecked;
}
}
@ -1108,7 +1235,6 @@ public record GroupBitArrayControl(BitListArrayElementViewModel Element) : Group
}
public record GroupButtonControl(ButtonArrayElementViewModel Element) : GroupFixedHeighteControl(), IGroupControl {
// TODO CanExecute
private bool isHover;
public void MouseEnter(TableGroupPanel parent, MouseEventArgs e) { isHover = true; parent.InvalidateVisual(); }
public void MouseDown(TableGroupPanel parent, MouseButtonEventArgs e) { }
@ -1117,7 +1243,7 @@ public record GroupButtonControl(ButtonArrayElementViewModel Element) : GroupFix
public void MouseExit(TableGroupPanel parent, MouseEventArgs e) { isHover = false; parent.InvalidateVisual(); }
public void Render(RenderContext context) {
context.DrawTextButton(new Rect(2, YOffset + 2, Width - 4, Height - 4), context.CurrentFontSize - 2, Element.Text, isHover);
context.DrawTextButton(new Rect(2, YOffset + 2, Width - 4, Height - 4), context.CurrentFontSize - 2, Element.Text, isHover, Element.Command.CanExecute(null));
}
public void KeyInput(TableGroupPanel parent, KeyEventArgs e) { }
@ -1125,7 +1251,6 @@ public record GroupButtonControl(ButtonArrayElementViewModel Element) : GroupFix
}
public record GroupPythonButtonControl(PythonButtonElementViewModel Element) : GroupFixedHeighteControl(), IGroupControl {
// TODO CanExecute
private bool isHover;
public void MouseEnter(TableGroupPanel parent, MouseEventArgs e) { isHover = true; parent.InvalidateVisual(); }
public void MouseDown(TableGroupPanel parent, MouseButtonEventArgs e) { }
@ -1134,14 +1259,9 @@ public record GroupPythonButtonControl(PythonButtonElementViewModel Element) : G
public void MouseExit(TableGroupPanel parent, MouseEventArgs e) { isHover = false; parent.InvalidateVisual(); }
public void Render(RenderContext context) {
context.DrawTextButton(new Rect(2, YOffset + 2, Width - 4, Height - 4), context.CurrentFontSize - 2, Element.Name, isHover);
context.DrawTextButton(new Rect(2, YOffset + 2, Width - 4, Height - 4), context.CurrentFontSize - 2, Element.Name, isHover, Element.CanExecute());
}
public void KeyInput(TableGroupPanel parent, KeyEventArgs e) { }
public void TextInput(TableGroupPanel parent, TextCompositionEventArgs e) { }
}
// next most important controls:
// SpriteIndicatorElementViewModel
// tuples
// calculated fields

View File

@ -207,6 +207,8 @@ namespace HavenSoft.HexManiac.WPF.Implementations {
public Task RunBackgroundWork(Action action) => Task.Run(action);
public IDelayWorkTimer CreateDelayTimer() => new DelayWorkTimer();
#endregion
public string RequestNewName(string currentName, string extensionDescription = null, params string[] extensionOptions) {