263 Commits
Author SHA1 Message Date
Nunuhara Cabbage 04333ad689 Merge pull request #378 from kichikuou/bb-misc
Miscellaneous changes for Busou Shoujotai Blade Briders
2026-09-21 20:42:53 -07:00
kichikuou 960c97ecc9 Update game_compatibility.md
- Mark Busou Shoujotai Blade Briders as supported.
- Extend the table through Rance X.
2026-09-22 11:19:26 +09:00
kichikuou e61f4b3c42 Math: Add Asin() and Acos() 2026-09-22 11:18:54 +09:00
kichikuou 03f9e7e87e Implement SystemService.GetSystemBufferName 2026-09-22 11:18:54 +09:00
Nunuhara Cabbage 87e4bebb12 Merge pull request #377 from kichikuou/SengokuRanceFont
SengokuRanceFont: Fall back to Gothic TTF for missing FNL glyphs
2026-09-21 19:09:55 -07:00
Nunuhara Cabbage 3a0dc49340 Merge pull request #376 from kichikuou/bb-parts
PartsEngine fixes for Busou Shoujotai Blade Briders
2026-09-21 19:09:37 -07:00
kichikuou e93cddad76 SengokuRanceFont: Fall back to Gothic TTF for missing FNL glyphs
SengokuRanceFont.DLL falls back to MS Gothic for characters whose glyphs
are absent from the FNL font. Reproduce this behavior to enable Japanese
translations of mods based on the MangaGamer release of Sengoku Rance.

Add an HLL-local composite font that wraps each selected FNL font_size
together with a FreeType Gothic font_size. The composite font checks for
each glyph in the FNL font and delegates rendering and size to Gothic
when the glyph is missing.

Scale fallback glyphs down and adjust their vertical position so that
they align with the FNL text line.
2026-09-22 10:24:48 +09:00
kichikuou 461eca1a85 parts: Inherit alpha clipper from parent parts
Move alpha_clipper_parts_no into inherited part parameters so child
parts use their parent's clipper unless they explicitly override it.

Bump the save version to 7.
2026-09-16 12:47:59 +09:00
kichikuou 6ea8af95ba parts: Fix global params after releasing a parent
parts_release detached children without marking them dirty, leaving
them with global parameters inherited from the released parent.
Furthermore, parts_update_component skipped global parameter updates
for parentless parts.

Mark orphaned children dirty and combine parentless parts against the
root parameters.
2026-09-16 12:46:07 +09:00
kichikuou 167d75d092 parts: Implement PartsFunc 112 (GetText) 2026-09-16 10:14:38 +09:00
kichikuou 275d71712e parts: Honor FLAT graphic key alignment
Offset the sprite so that the origin matches the anchor selected by the
graphic key's align field. Use the source rectangle dimensions when an
area is specified, and the full texture dimensions otherwise.
2026-09-16 10:14:37 +09:00
kichikuou d68baa74a0 parts: Save and load 3DLayer components
Serialize the SealEngine plugin bound to the component. Instances now
remember the name so referenced assets can be reloaded.

Bump the parts save format version to 6.
2026-09-16 10:14:26 +09:00
kichikuou 267f7bb762 parts: Support PartsFunc func_id assignments in Blade Briders
Blade Briders inserts three layer functions at func_id 3-5 into the
assignment used by Rance9, shifting all subsequent func_ids up by three.
Evenicle uses the same assignment as Blade Briders.

Use this assignment internally and add three to Rance9 func_ids before
dispatch. Also implement the functions 3-5.
2026-09-15 08:36:47 +09:00
kichikuou addc26ca45 parts: Implement PartsEngine.GetComponentSpeedupRateByMessageSkip 2026-09-15 08:36:39 +09:00
Nunuhara Cabbage bc745035e1 Merge pull request #375 from kichikuou/bb-3d
3D fixes for Busou Shoujotai Blade Briders
2026-09-13 08:05:01 -07:00
kichikuou 11ad7a583a 3d: Implement UV tiling
Read ColorMap UV tiling parameters from POL materials and apply separate
tiling factors to base and blend texture coordinates.
2026-09-13 11:20:54 +09:00
kichikuou 9654950ab4 3d: Implement SealEngine.SetDrawOption 1 (lighting)
When set to zero, render all meshes, including billboards and particle
meshes, as if they had the (nolighting) attribute.
2026-09-13 09:40:42 +09:00
kichikuou 021187752b 3d: Implement SealEngine.{Set,Get}InstanceGrayscaleRate
Add a per-instance grayscale rate that desaturates the final fragment
color after tone mapping.
2026-09-13 09:40:42 +09:00
kichikuou 5cc163ab16 3d: Load billboard frame images by name
In SealEngine, the frame images of a billboard instance are PNG files in
the archive, named "<name>\<basename>.png", "<name>\<basename>[1].png"
and so on, and the motion frame number is the index into them.

This also changes the lifetime management for billboard motions. A
billboard's motion holds the frame animation state of the instance,
which the game sets up before calling Load, so keep it across loads
and free it in free_instance.
2026-09-13 09:40:37 +09:00
kichikuou e14b4bca79 3d: Implement per-instance billboard vertices and UVs
Store the four billboard vertex positions and UVs in each RE_instance
and use them for regular billboard rendering. The static billboard mesh
is kept for billboard particles.
2026-09-12 11:00:57 +09:00
kichikuou 02f29b6c6d 3d: Implement SealEngine.{Set,Get}EdgeReductionRate 2026-09-12 11:00:49 +09:00
Nunuhara Cabbage fa09b0059f Implement PartsEngine.SetSpeedupRateByMessageSkip
Speeds up animations while message skipping.
2026-08-25 18:48:58 -07:00
Nunuhara Cabbage 4233c7d294 Fix EFFECT_CROSSFADE_MOSAIC
Long overdue fix for this effect, which is heavily used in Sengoku
Rance. The origin for the grid is now properly at the center of the
screen, as in the original engine.
2026-08-24 17:44:44 -07:00
Nunuhara Cabbage b30a107fda Implement EFFECT_VWAVE_SCROLL_CROSSFADE
This is the same as EFFECT_VWAVE_CROSSFADE, except with a different
maximum amplitude and the screen scrolls vertically.

(Used in Rance 9 when transitioning to a flashback scene.)
2026-08-23 10:39:03 -07:00
Nunuhara Cabbage bf3430dbad Improve EFFECT_VWAVE_CROSSFADE
Improve the math in this effect so that it matches Alicesoft's
implementation. This is almost a pixel-perfect recreation of the effect.
The only difference is that our waves are rendered with a smoother edge.
2026-08-23 10:11:29 -07:00
Nunuhara Cabbage 135cae216f Mitigate UI performance issue in Rance 9
Add a game-specific hack to mitigate poor performance when performing
certain UI operations in Rance 9.

Without this hack, the game creates an array of 990000 entries and then
calls Array.Find on it whenever it acquires or releases a parts number.
This was especially painful in debug builds, where the game would freeze
for 1-2 seconds when switching to the items screen for example.

The hack replaces the Array.Find calls with arithmetic.
2026-08-20 19:23:35 -07:00
Nunuhara Cabbage 05820dc536 parts: RemoveController returns delegate indices
The returned integer array contains p->delegate_index, not p->no.

I don't think this caused any bugs, because the game code rejects
returned indices that are > the delegate array length, and the delegate
index appears to always be -1 when this function is called.
2026-08-16 11:05:43 -07:00
Nunuhara Cabbage e76762d11d Implement EFFECT_DOWN_UP_CROSSFADE
Used in Rance 9 (when Hunty disappears).
2026-08-14 21:51:11 -07:00
Nunuhara Cabbage 5fb3c49f75 parts: clear cached font when rerendering text
Fixes an issue in Rance 9 (English version) where name plate text would
display at the wrong size.
2026-08-14 17:26:25 -07:00
Nunuhara Cabbage 5d032d739e Fix various memory leaks 2026-08-14 15:17:31 -07:00
Nunuhara Cabbage e3bf653165 Fix crash in Shaman's Sanctuary
Shaman's Sanctuary uses GoatGUIEngine.AddMotionCG with a CG index of 0
to clear the CG.
2026-08-14 12:40:00 -07:00
Nunuhara Cabbage a19c200870 parts: clean up text layout logic 2026-08-14 11:30:52 -07:00
Nunuhara Cabbage 5cbc697f23 parts: use bold width for text layout
Fixes the remaining issues with item descriptions in Rance 9.
2026-08-14 10:48:07 -07:00
Nunuhara Cabbage 4e20a499d4 parts: use ceilf(ch->advance) for text layout
Fixes an issue in English version of Rance 9 where item description text
was overly compressed. Now ChipmunkSpriteEngine.SP_GetFontWidth returns
the same values as the official engine.

There are still text layout bugs with item descriptions in Rance 9
(affecting both JP and EN versions), but this at least makes the text
readable.
2026-08-13 20:14:54 -07:00
Nunuhara Cabbage 0d4eb2a2a7 Merge pull request #373 from kichikuou/math-rand
Math: Replace rand() with mt19937
2026-08-13 18:28:18 -07:00
kichikuou d800e12f59 Math: Replace rand() with mt19937
On Windows RAND_MAX is 0x7fff, which makes Rand() and RandF() far too
coarse. Use mt19937 instead so that seeded sequences reproduce the
original engine's behavior.
2026-08-14 09:31:32 +09:00
Nunuhara Cabbage b1f8948b74 Merge pull request #372 from kichikuou/parts-load-text
parts: Fix loss of line breaks in saved multi-line text
2026-08-11 19:14:35 -07:00
Nunuhara Cabbage ebc319994e Merge pull request #371 from kichikuou/rance9-v2
parts: Reference construction process CGs by name
2026-08-11 19:13:18 -07:00
kichikuou 9bb871d9fd parts: Fix loss of line breaks in saved multi-line text
Text lines are saved individually without newlines. Insert an explicit
newline between lines on load so the original line structure is
restored. Also free the strings read from the save data.
2026-08-11 13:46:18 +09:00
kichikuou 62d38aaf59 parts: Reference construction process CGs by name
Rance 9's AFA archives have no ID field, so asset IDs are sequential
indices local to each archive. When the game loads an additional CG
archive, the ID spaces overlap: a name resolved to an ID against the
base archive later fetches a different CG from the additional archive.
Keep the CG name instead.

Bumps the save version to 5; version 4 and earlier saves still store IDs
and are converted on load.
2026-08-11 13:05:32 +09:00
Nunuhara Cabbage 5bb36c7524 Update libsys4
Fixes #370 via nunuhara/libsys4@cbc57ce
2026-08-09 19:00:36 -07:00
Nunuhara Cabbage 4dc9792632 Merge branch 'master' of github.com:nunuhara/xsystem4 2026-08-09 08:32:53 -07:00
Nunuhara Cabbage a7e973c0a9 Merge pull request #369 from kichikuou/rance9-v2
Resolve archive paths in asset_manager_load_archive
2026-08-08 20:09:26 -07:00
kichikuou 33f1295237 Resolve archive paths in asset_manager_load_archive
AFAFactory.LoadArchive passed the SJIS archive name straight through, so
the extra assets loaded by the Rance 9 v2.00 patch were never found.
Move the path resolution out of CGManager.LoadArchive so that both
callers share it.
2026-08-09 10:36:00 +09:00
Nunuhara Cabbage e699fb0aa2 DAP: ensure messages are null-terminated 2026-08-08 10:10:28 -07:00
Nunuhara Cabbage 4e0dd82776 Merge pull request #368 from kichikuou/libsys4
Update libsys4
2026-08-05 18:42:52 -07:00
kichikuou 77d9e77053 Update libsys4 2026-08-06 09:22:23 +09:00
Nunuhara Cabbage d65a1e6d9b Merge pull request #367 from kichikuou/rance9
game_compatibility.md: Add Rance 9 (JA) as supported
2026-08-04 18:50:56 -07:00
kichikuou 68e8257c27 game_compatibility.md: Add Rance 9 (JA) as supported 2026-08-04 20:52:41 +09:00
Nunuhara Cabbage 8e339d1537 Merge pull request #366 from kichikuou/save
PartsEngine: Stabilize save format for Rance 9
2026-08-02 11:12:12 -07:00
kichikuou a2524bafd5 PartsEngine: Stabilize save format for Rance 9
Bump the save version and always serialize controller state and the
Rance 9 parts fields. Load these fields based on the save version while
retaining compatibility with existing multi-controller saves.
2026-08-02 08:49:06 +09:00
Nunuhara Cabbage f146320e36 Merge pull request #365 from kichikuou/3d
Two 3D fixes for Rance 9
2026-08-01 06:25:54 -07:00
kichikuou 09a0e3329a 3d: Disable specular with zero power
Treat zero SpecularPower as disabling specular lighting instead of
producing a constant white highlight (pow(..., 0) = 1).
2026-07-29 08:50:18 +09:00
kichikuou b68e1622fd 3d: Implement DrawShadow mesh attribute
Also parse some other .opr attributes for features not implemented in
xsystem4.
2026-07-28 14:29:50 +09:00
Nunuhara Cabbage 433e3be3e6 Merge pull request #364 from kichikuou/rance9-bgi
KiwiSoundEngine: Fix BGM loop for Rance 9
2026-07-26 09:13:40 -07:00
kichikuou d5b27a0a47 KiwiSoundEngine: Fix BGM loop for Rance 9
Commit 03062d5 fixed audio lookup for Rance 9, but the new archive-data
preparation path bypassed BGI and WAI metadata. Pass the asset number
through that path so loop points are applied correctly.
2026-07-26 07:53:03 +09:00
Nunuhara Cabbage a38c55602d Merge pull request #363 from kichikuou/ffmpeg
movie_ffmpeg: Use nb_samples for audio frame length
2026-07-25 08:50:35 -07:00
Nunuhara Cabbage b3bcd79bae Merge pull request #362 from kichikuou/pl_mpeg
pl_mpeg: Support MPEG-2 program stream headers
2026-07-25 08:50:00 -07:00
kichikuou f0c4db718d movie_ffmpeg: Use nb_samples for audio frame length
Audio linesize may include unused buffer space. Using it as the sample
count caused short Vorbis frames to replay stale samples and slowed down
audio playback.
2026-07-22 09:07:30 +09:00
kichikuou d17fc30731 pl_mpeg: Support MPEG-2 program stream headers
Fortress.alm from Rance IX uses an MPEG-2 Program Stream containing
MPEG-1 video and MPEG-1 Layer II audio. As far as I checked, it is the
only .alm movie released in Japan that does not use an MPEG-1 Program
Stream.

The existing pl_mpeg decoders support its elementary streams, but the
demuxer rejects its MPEG-2 pack and PES headers. Extend the demuxer to
parse those headers and the PTS/DTS combinations used by Fortress.alm.
2026-07-22 07:31:44 +09:00
Nunuhara Cabbage ca2254360c Merge pull request #361 from kichikuou/libdeflate
Use libsys4 zlib wrappers
2026-07-19 21:21:54 -07:00
kichikuou 81f9113906 Use libsys4 wrappers for zlib operations
Replace direct one-shot zlib compression and decompression calls with
the libsys4 wrappers. Use exact-size decompression where file formats
provide the uncompressed size.

File2.c continues to depend directly on zlib because it uses streaming
inflate.
2026-07-20 12:53:19 +09:00
Nunuhara Cabbage d63e71e85d Merge pull request #360 from kichikuou/numpad
input: Handle numpad keys with NumLock disabled
2026-07-16 21:03:56 -07:00
kichikuou 6d05546c2c input: Handle numpad keys with NumLock disabled
When NumLock is disabled, Windows reports numpad keys as navigation
virtual-key codes in the WM_KEYDOWN message. SACT2 games rely on this
behavior.

Translate SDL keypad scancodes to the corresponding SACT key codes in
that state, and map keypad Enter to VK_RETURN.

Fixes #359.
2026-07-15 08:36:08 +09:00
Nunuhara Cabbage e260b82828 Merge pull request #358 from kichikuou/3d-debug
debugger: Add "3d" debugger commands
2026-07-13 17:55:14 -07:00
kichikuou c0cdf4f070 debugger: Add "3d" debugger commands
Add the following commands to the debugger:

  3d list [all]                      List plugins and instances
  3d plugin [<plugin>]               Show or select the current plugin
  3d show [<instance>]               Dump plugin or instance state
  3d set <instance> draw <0|1>       Enable or disable drawing
  3d set <instance> pos <x> <y> <z>  Set the instance position
  3d help                            Show command help

The show and set commands operate on the current plugin. If only one
plugin exists, it is selected automatically. Changes made with set are
rendered immediately.
2026-07-12 13:37:44 +09:00
Nunuhara Cabbage 74461f5b9c Merge pull request #357 from kichikuou/issue-356
parts: Fail construction build without target texture
2026-07-06 18:24:53 -07:00
kichikuou d8479ad80a parts: Fail construction build without target texture
If AddCreateCGToProcess fails for a missing CG, no create op is added to
the construction process. Game scripts still add drawing operations and
then build the process, leaving those operations with no destination
texture and causing an incomplete framebuffer error.

This makes construction build operations return false as soon as an
operation cannot be executed.

Fixes #356.
2026-07-07 08:52:17 +09:00
Nunuhara Cabbage e8bd5ab036 Merge pull request #355 from kichikuou/relative-user-config-paths
Resolve game config paths relative to game dir
2026-07-05 11:23:53 -07:00
kichikuou 05fee4ba83 Resolve game config paths relative to game dir
Game-specific .xsys4rc files are loaded from the game directory, so
relative paths in that file are most useful when interpreted from the
same directory. This allows patches that ship external fonts to
reference them with relative paths.
2026-07-05 08:03:29 +09:00
Nunuhara Cabbage bf3b3416fe Merge pull request #354 from kichikuou/edge
SealEngine: Implement outline rendering options
2026-06-27 08:12:00 -07:00
kichikuou fb3d0bbd3e 3d: Gate edge drawing on per-instance flag
In both TapirEngine and SealEngine an instance is outlined only when the
global edge draw option and its per-instance edge flag are both on. The
per-instance flag is exposed via Set/GetInstanceDrawParam, whose edge
index differs by engine (Tapir 0, Seal 2).

For the global option the engine only distinguishes on/off, so drop the
RE_draw_edge_mode enum.
2026-06-26 09:19:15 +09:00
kichikuou 507b65fc9c SealEngine: Implement SetEdgeLength/SetEdgeColor
In SealEngine the outline length and color are plugin-wide values set
via SetEdgeLength/SetEdgeColor, not the per-mesh .opr parameters used by
TapirEngine.
2026-06-25 16:32:54 +09:00
Nunuhara Cabbage 24df15b9e9 Merge pull request #353 from kichikuou/delegate
VM: Check function signature in DG_STR_TO_METHOD
2026-06-20 17:55:08 -07:00
kichikuou fab789df65 VM: Check function signature in DG_STR_TO_METHOD
Only resolve the method when the function's signature matches the
delegate type, returning 0 otherwise.

Also, DG_SET / DG_NEW_FROM_METHOD / DG_ADD should not add function 0.
2026-06-21 06:56:24 +09:00
Nunuhara Cabbage 3012faa6ff Merge pull request #352 from kichikuou/lighting
SealEngine: Add lighting support
2026-06-20 06:22:14 -07:00
kichikuou 6dbb5baf19 SealEngine: Implement filmic tone mapping
Apply the Uncharted 2 tone curve [1] from light_params[0..7] as the
final fragment operation.

[1] https://filmicworlds.com/blog/filmic-tonemapping-operators/
2026-06-20 11:41:56 +09:00
kichikuou db8279ebc3 SealEngine: Implement hemisphere lighting
This implements the sky/middle/ground hemisphere ambient driven by
light_params[8..19].

SealEngine has no separate specular-light instance; its specular instead
shares the hemisphere light vector.
2026-06-20 11:41:56 +09:00
kichikuou 250bc82818 SealEngine: Enable light scattering
Reuse ReignEngine's existing scattering shader code. The ls_* uniforms
are sourced from .lit parameters.
2026-06-20 11:41:56 +09:00
kichikuou 4843e4e87a SealEngine: add light parameter storage and HLL bindings
SealEngine manages a 41-float light parameter set (serialized as .lit
file) for tone mapping, hemisphere lighting, and light scattering. This
adds the storage and HLL plumbing, without wiring values into shaders.

- A DIRECTIONAL_LIGHT instance owns a parameter set, filled by loading a
  .lit file (RE_instance_load).
- SealEngine.StoreInstanceLightParam() reflects that presets into the
  plugin's master buffer, which is used by the renderer.
- SealEngine.LoadInstanceLightParam() copies the master back into an
  instance, and Reset()/Set()/Get() operate directly on the master.
2026-06-20 11:41:56 +09:00
kichikuou 1051294247 SealEngine: Implement SpecularColor and SpecularPower .opr attributes
Parse per-mesh SpecularColor and SpecularPower attributes from .opr
files, and apply them to specular lighting.
2026-06-20 11:41:56 +09:00
Nunuhara Cabbage fa98a76c42 Merge pull request #351 from kichikuou/msgskip
MsgSkip: Initialize in _PreLink when Init() is absent
2026-06-19 17:30:35 -07:00
kichikuou d073875d94 MsgSkip: Initialize in _PreLink when Init() is absent
Some games (e.g. Rance 01, Rance 9) only export GetFlag and SetFlag, so
Init() is never called. Initialize from _PreLink with a default save
file name in that case.
2026-06-18 08:10:49 +09:00
Nunuhara Cabbage 2b67a9e000 Merge pull request #350 from kichikuou/3d
3d: Various improvements for Rance 9
2026-06-14 13:28:48 -07:00
kichikuou b62edb7188 SealEngine: Support HeightDetection=true .opr attribute
In TapirEngine, CalcIntersectEyeVec() only worked on models with a
"collision" mesh. In SealEngine, a raycast collider is built from the
union of meshes flagged HeightDetection=true in .opr.

Also return the nearest intersection instead of the first.
2026-06-13 20:30:45 +09:00
kichikuou 85547ef4f4 3d: Remove color_add uniform
Since the ambient term is now added unconditionally, the s3de per-object
additive color can be folded into instance_ambient.
2026-06-12 15:00:40 +09:00
kichikuou f108b7f436 3d: Apply ambient and vertex color across all diffuse modes
The EMISSIVE and ENV_MAP diffuse modes did not apply the ambient term or
vertex color (color_mod) to the fragment. In the original TapirEngine /
SealEngine, all diffuse modes contribute ambient and vertex color.

This fixes a bug where the movement/attack range indicator color did
not change in battle scenes.
2026-06-12 15:00:40 +09:00
kichikuou 90c3ab5008 3d: Apply per-instance diffuse to mesh color
SetInstanceDiffuse has two roles depending on the instance type: for
directional lights it is the light color, and for meshes it is a
multiplier on the mesh color. xsystem4 honored only the former.

Treat inst->diffuse as color multiplier, via the diffuse_mod uniform.
2026-06-12 15:00:40 +09:00
kichikuou 7b9565fe25 3d: Add reset_draw_uniforms() to prevent per-draw state leaks
This adds a helper that resets every per-draw uniform to a neutral
default and call it at the start of the billboard, .pae and .3de paths.

This fixes uv_scroll and use_blend_texture leaks from a previously
rendered model.
2026-06-12 15:00:40 +09:00
kichikuou 4dcc50d6ca 3d: Build models from MarmotModelEngine.Update()
In TapirEngine and SealEngine, model updates are drived by
MarmotModelEngine.Update(). RE_render() now only renders.
2026-06-12 15:00:40 +09:00
kichikuou 5c47ad5aae SealEngine: Implement ZWrite .opr attribute 2026-06-12 15:00:39 +09:00
Nunuhara Cabbage 062713f70e Merge pull request #349 from kichikuou/parts
Implement two PartsFuncs, and minor fixes
2026-06-11 17:22:02 -07:00
kichikuou 0e8bd36407 SACT2: Update draw plugins during TRANS_Update()
This fixes the issue where the 3D plugin freezes during transitions.
2026-06-10 17:21:04 +09:00
kichikuou 28554137cc Stoat/Chipmunk: Silence KeepPreviousView_Set/GetMode warnings
KeepPreviousView and its mode are purely a rendering performance knob
(incremental rendering optimization). Toggling the mode never affects
rendering correctness, so leaving these unimplemented is harmless and
warning about them is just noise.
2026-06-10 17:17:08 +09:00
kichikuou 32024bc805 PartsEngine: Fix "Invalid controller number" error after vm_reset
PE_Reset() zeroed the controller stack, leaving it with no default
controller. Since PE_Reset() does not clear parts_engine_initialized,
the subsequent PE_Init() returns early so the stack stays empty.

Reset the stack with ctrl_stack_init() instead, which re-creates the
default controller. ctrl_stack_fini() is now unused and removed.
2026-06-10 17:17:08 +09:00
kichikuou ec9bd95dc9 PartsEngine: Implement PartsFunc 3 (motion Pause/Resume)
Wire up PartsFunc 3 (PauseMotion) to a new PE_PauseMotion(). While
paused, PE_UpdateMotionTime stops advancing the motion clock.
2026-06-10 17:17:05 +09:00
kichikuou 8dee7d8658 PartsEngine: Implement PartsFunc 6 (SaveThumbnail)
Save a reduced copy of the main surface to a file. The image is written
as QNT under the save directory.

To avoid aliasing at large reductions, the surface is downscaled by
repeatedly halving before the final stretch.
2026-06-10 17:17:01 +09:00
Nunuhara Cabbage 0f73ded483 Merge pull request #348 from kichikuou/3de
3de: Add support for billboard and polygon objects
2026-06-07 07:09:42 -07:00
kichikuou 847987ec1b 3de: Add support for billboard and polygon objects
At spawn, init_particle() samples its fixed properties from a
deterministic PRNG into s3de_particle, so playback is reproducible.

Each frame, update_object_states() interpolates the emitter's animated
properties into s3de_object_state.

To draw, render_s3de_effect() combines the two: for each live particle
s3de_billboard_world_transform() or s3de_mesh_world_transform() derives
a world matrix from the lifetime state computed by eval_particle(),
together with a posture basis that orients the quad or mesh. Objects are
drawn sorted by distance from the camera.
2026-06-04 09:41:49 +09:00
Nunuhara Cabbage ec4b608877 Merge pull request #347 from brombrom-hani/parts-sprite-deform
parts: impl sprite_deform for SetPartsCG
2026-06-01 17:55:27 -07:00
BromBrom-Hani 12735d5c8c parts: sprite_deform for SetPartsCG string index
Keeps the PE_SetPartsCG_by_string_index consistent with the rest of the SetPartsCG variants
2026-06-01 08:43:24 +02:00
Nunuhara Cabbage 22d58d977a Merge pull request #346 from kichikuou/3de-parser
SealEngine: Add .3de parser, effect framework, camera, and sound
2026-05-31 18:11:38 -07:00
Nunuhara Cabbage 20e0d00661 Merge pull request #345 from kichikuou/bone
3d: Match MOT bones to POL bones by array index
2026-05-31 18:10:21 -07:00
BromBrom-Hani a706a41dcb parts: impl sprite_deform for SetPartsCG 2026-05-31 12:22:36 +02:00
kichikuou bf0b2bb44f SealEngine: Add .3de parser, effect framework, camera, and sound
.3de is the particle/effect format used by SealEngine. Like the
ReignEngine's .pae, it is a text format describing an effect as a set of
emitters, but the two differ enough in design that .3de lives in its own
file (s3de.c) rather than being folded into particle.c.

This commit lays the groundwork:

- Parse .3de text.
- Integrate effect loading, playback, and caching into the existing
  infrastructure.
- Apply the camera object's per-frame override to RE_camera.
- Play the companion sound asset.

Billboard and polygon-object rendering will be added separately.
2026-05-31 08:22:01 +09:00
kichikuou 916daec6b2 3d: Match MOT bones to POL bones by array index
The original {Reign,Tapir,Seal}Engine apply motion bones to model bones
purely by array position, never consulting bone name or bone_id.

This fixes bone animation of "Shizuka & Nagi" model in Rance 9.
2026-05-29 07:17:41 +09:00
Nunuhara Cabbage 1fcccb766c Merge pull request #344 from kichikuou/passed_time
PartsEngine: clamp passed_time to 0 in PE_UpdateComponent
2026-05-27 21:00:56 -07:00
kichikuou aa112849de Remove passed_time parameter from parts_render_update 2026-05-28 07:39:15 +09:00
kichikuou 8ca1a31ccc PartsEngine: clamp passed_time to 0 in PE_UpdateComponent
After loading a save, the game script may give a negative passed_time
because it computes the delta time using an absolute system.GetTime()
value stored in the save data.

This fixes hang on load in Rance 9.

This also removes the equivalent workaround in parts_render_update for
Rance 01, since the new clamp covers that case as well.
2026-05-27 12:42:50 +09:00
Nunuhara Cabbage 2c622bb331 Merge pull request #343 from kichikuou/mpr
SealEngine: Add .mpr (motion material keyframes) support
2026-05-25 19:07:24 -07:00
kichikuou c01a27583c SealEngine: Add .mpr (motion material keyframes) support
.mpr is an optional sidecar to .mot used by SealEngine, replacing .txa
used in ReignEngine / TapirEngine. While .mot drives bone poses, .mpr
drives material properties on a per-frame timeline.

Supported keys, all keyed by frame number:

  Object{MulAlpha,MulDiffuse,AddAmbient} : applied to the whole model
  Mesh{MulAlpha,MulDiffuse,AddAmbient}   : applied per named mesh
  MeshTextureAnime                       : swap diffuse texture index

Colors and alpha use linear interpolation between keys. Out-of-range
frames hold the nearest endpoint.

Mesh* tracks act as per-mesh tints and do not leak to other meshes
that happen to share a material. MeshTextureAnime is the exception:
it rewrites the shared material's diffuse slot, so all meshes using
that material animate in sync (e.g. in Rance 9, hanny_black_move.mpr
lists only hibana_R but hibana_L animates together).
2026-05-22 13:57:02 +09:00
Nunuhara Cabbage ced4daa331 Merge pull request #342 from kichikuou/camera
3d: Fix camera yaw direction and rotation order
2026-05-19 20:41:33 -07:00
kichikuou 9eee82c13b 3d: Fix camera yaw direction and rotation order
The camera view direction was computed incorrectly in two ways:

1. glm_euler (XYZ order) applies pitch around world X. The original
   engine uses YXZ where pitch is around the camera's local X after yaw.

2. SetCameraAngle negated the input yaw.
2026-05-20 07:10:41 +09:00
Nunuhara Cabbage f53ec98d53 Merge pull request #340 from kichikuou/emitter
Flat: Add particle emitter support
2026-05-18 22:19:56 -07:00
kichikuou 32f4253948 Flat: Guard against division by zero in particle time calculation 2026-05-19 11:38:36 +09:00
kichikuou cb0b969492 Flat: Add particle emitter support
This implements rendering of FLAT_LIB_EMITTER libraries: timed spawning,
per-particle motion, fade, and 3-axis rotation.

For each visible emitter, render_flat_emitter() builds an emitter base
matrix from the ancestor key stack so particles follow their parent
timelines. parent_key_mode chooses between filtering ancestor properties
via the emitter's inherit_* flags (SELECTIVE) and using ancestor poses
wholesale (ANCESTOR_CURRENT / ANCESTOR_AT_BIRTH). For each birth frame
in the lifetime window, parts_flat_foreach_emitter_particle() reseeds
the RNG from (rand_seed, birth_frame), making particles deterministic
across re-renders, and yields each particle's pose, which is composed
with the emitter transform and submitted as a quad through the standard
parts texture pipeline.
2026-05-19 07:12:11 +09:00
Nunuhara Cabbage 0030e28471 Merge pull request #339 from kichikuou/parts-3dlayer-z
Parts: Fix Z-order of 3DLayer and Movie parts
2026-05-16 07:46:35 -07:00
kichikuou 224db2f9cc Parts: Fix Z-order of 3DLayer and Movie parts
3DLayer and Movie parts use a SACT sprite as their render target (for
SealEngine output or movie frames). That sprite was registered into
the scene at z=0, so the content was drawn at z=0 instead of (or in
addition to) the parts engine's render path at the parts' configured Z.

Hide the bound SACT sprite from the scene so the parts engine is the
sole renderer of its texture, and add a parts_render case for 3DLayer
that composites the bound sprite's texture through parts_render_cg.
2026-05-16 12:44:17 +09:00
Nunuhara Cabbage 33d07c190e Merge pull request #338 from kichikuou/color-propagation
Flat: Fix color propagation for nested flat TIMELINE libraries
2026-05-12 17:11:52 -07:00
kichikuou a4dd6a42df Flat: Fix color propagation for nested flat TIMELINE libraries
Propagate parent keys' mul/add color parameters to child TIMELINE
library rendering.
2026-05-12 10:25:03 +09:00
Nunuhara Cabbage da268288c3 Merge pull request #337 from kichikuou/flat
Parts: Stop motion support and rendering fixes
2026-05-10 09:58:02 -07:00
kichikuou f535223988 Flat: Support angle_x/angle_y rotation and reverse_lr/reverse_tb
The original engine implements proper perspective projection, but we
use simpler orthographic foreshortening. In Rance 9, the visual
difference is barely noticeable.
2026-05-09 12:12:38 +09:00
kichikuou f9087eb7ac Flat: Fix area_x/area_y leaking into screen position
area_x/area_y select a sub-rectangle of the texture, but should not
shift the on-screen position. Translate the quad by (-area_x, -area_y)
to cancel the offset the sub-rect's top-left would otherwise introduce.
2026-05-07 12:42:08 +09:00
kichikuou 636743478c Flat: Propagate parent TIMELINE's draw_filter
A parent key's draw_filter had no effect on CG/STOP_MOTION leaves
inside descendant TIMELINE libraries. Resolve the effective filter
at each key (own if non-NORMAL, otherwise inherited) and thread it
through the render chain.
2026-05-07 12:18:35 +09:00
kichikuou a2f087b7f9 Flat: Add support for FLAT_LIB_STOP_MOTION
FLAT_LIB_STOP_MOTION represents a sprite animation as a sequence of CG
frames. A stop_motion library specifies a base CG file (e.g. "foo.png");
at load time, parts_flat_load() discovers numbered frame variants
("foo_01.png", "foo_02.png", ...) and builds a frame list.

At render time, the active frame is resolved from the local timeline
clock using three parameters:
  - span: number of timeline ticks each frame is displayed
  - loop_type: 0 = play once and stop at last frame,
               1 = loop forward,
               2 = ping-pong (forward then backward)
  - local: the timeline's current local time
2026-05-07 11:23:53 +09:00
Nunuhara Cabbage 07bd9c7438 Merge pull request #336 from kichikuou/draw_filter
Parts: support and fix multiply/screen draw_filter
2026-05-03 07:54:48 -07:00
Nunuhara Cabbage 028c907a12 Merge pull request #335 from kichikuou/InputString
Fix save comment editing in Rance 02
2026-05-03 07:54:29 -07:00
kichikuou c45bb3a736 Parts: support multiply/screen draw_filter for parts_render_cg 2026-05-03 12:39:43 +09:00
kichikuou 484f383826 Parts: fix multiply/screen draw_filter to respect alpha
The multiply and screen blend functions used in render_flat_item
ignored the source alpha, so transparent pixels still modulated the
destination color instead of leaving it untouched.

Add a draw_filter uniform to the parts fragment shader and have it
output an alpha-baked source color, so that the framebuffer result
after the GL blend equation interpolates correctly in alpha:

- multiply: shader outputs src = mix(vec3(1.0), mod_color, alpha)
  blend (GL_DST_COLOR, GL_ZERO) gives src*dst,
  i.e. mix(dst, mod_color*dst, alpha)
- screen: shader outputs src = mod_color * alpha
  blend (GL_ONE, GL_ONE_MINUS_SRC_COLOR) gives src + (1-src)*dst,
  i.e. mod_color*alpha + (1 - mod_color*alpha)*dst

In both cases alpha=0 leaves the destination untouched, alpha=1
gives the full multiply/screen result.

Also switch the surface-area mask from multiplication to discard,
so pixels outside the rect leave the destination intact under
multiply blending (where multiplying src by 0 would otherwise
zero out dst).
2026-05-03 12:39:41 +09:00
kichikuou af12e19859 Fix missing src/parts/message.c in CMakeLists.txt 2026-05-03 07:37:02 +09:00
kichikuou e83cb1c91e InputString: Handle Backspace key to delete last character
This adds a key handler facility to input.c so that the InputString
can intercept the Backspace key while the IME is open, and remove the
last character from the result string. Backspace is ignored while the
IME has composing text, so it does not interfere with IME editing.
2026-05-03 07:13:47 +09:00
kichikuou 4c4a62e1ed Fix InputString.GetResultString() in Rance 02
The newer version (from Boku Dake no Hokenshitsu onwards) of
InputString.GetResultString() does not clear its internal string.

This fixes the issue in Rance 02 where the text always remained empty
when editing save comments.
2026-05-03 07:12:33 +09:00
kichikuou f01e41be12 Implement InputString.SetResultString
Used in Rance 02.
2026-05-03 07:12:33 +09:00
Nunuhara Cabbage 6bb00a268c Merge pull request #334 from kichikuou/vm
Fix heap use-after-free during method calls in Rance 9 and later
2026-05-01 20:22:42 -07:00
kichikuou 9ed1f52627 VM: Keep struct page alive during method calls for AIN v6.1+
This fixes heap use-after-free in the music mode in Rance 9.

Hold a reference to the struct page (`this` pointer) for the duration
of a method call. Without this, if the sole external reference to an
object is dropped during one of its method calls (e.g. an event handler
that destroys its own parent), the object and its members are freed
immediately, causing use-after-free when the VM tries to access them
afterward.

This matches the behavior of the original VM, which was introduced in
Rance9.exe and later titles.

The fix is gated on AIN v6.1+ (AIN v6 with a MSG1 section) to match
the original. Applying it unconditionally would change the heap refcount
state visible to resume saves, potentially breaking existing saves for
older games.
2026-05-02 07:56:47 +09:00
Nunuhara Cabbage 59789c0a8a Merge pull request #333 from kichikuou/text
PartsEngine: Fix text issues in Rance 9
2026-04-28 17:54:53 -07:00
kichikuou 13d0c51351 PartsEngine: Fix text line layout
line_space was stored but never used during rendering or size
calculation. Apply it as inter-line spacing in both the size calculation
and the render path. Also initialize empty lines with the current text
style height so that newline-only lines occupy the correct space.

Change line_space from unsigned to int because negative values can be
set.
2026-04-28 11:18:58 +09:00
kichikuou 46bdbb05ef PartsEngine: Re-render text when font properties change
This fixes the rendering of the sample message in the "Message Color"
configuration of Rance 9.

PE_SetPartsFont* and PE_SetText*Space functions only modified the text
style struct without re-rendering existing character textures. Since
character glyphs are baked into textures at append time, subsequent
style changes had no visible effect.
2026-04-28 11:15:00 +09:00
Nunuhara Cabbage 234b04b460 Merge pull request #332 from kichikuou/SealEngine
SealEngine: Implement and stub various functions
2026-04-25 20:37:52 -07:00
Nunuhara Cabbage ae9544a1ac Merge pull request #331 from kichikuou/POLv4
SealEngine: Support POL v4 format
2026-04-25 20:37:33 -07:00
kichikuou 6509dca693 Add stub for SystemService_GetAntiAliasingMode 2026-04-26 11:26:20 +09:00
kichikuou bf455de35d SealEngine: Implement MagSpeed functions
mag_speed is a per-plugin integer (default 1) set by SetMagSpeed on
all existing plugins. use_mag_speed is a per-instance flag (default
true) set by SetInstanceUseMagSpeed. When use_mag_speed is true,
motion delta_frame is multiplied by mag_speed.
2026-04-26 11:26:20 +09:00
kichikuou 511f6a4bfa SealEngine: Implement and stub various functions 2026-04-26 11:26:17 +09:00
kichikuou 4239345289 SealEngine: Support POL v4 format
POL v4 introduces material groups with texture blending. A material's
sub-material can be a "blend group" containing exactly two leaf
materials, whose textures are blended using per-vertex weights for
smooth terrain transitions.
2026-04-26 10:03:09 +09:00
Nunuhara Cabbage 955950198e Merge pull request #330 from kichikuou/3dlayer
PartsEngine: Implement 3DLayer parts type
2026-04-25 09:35:14 -07:00
kichikuou 63ab438391 PartsEngine: Implement 3DLayer parts type
This adds PARTS_3DLAYER type that creates a SealEngine plugin bound to a
SACT sprite.
2026-04-25 12:06:01 +09:00
Nunuhara Cabbage 1dd37559bb Merge pull request #329 from kichikuou/drag
PartsEngine: Implement drag-and-drop processing
2026-04-24 18:42:52 -07:00
kichikuou f52f4bd1f0 PartsEngine: Simplify parts_msg_push() calls 2026-04-25 07:49:08 +09:00
kichikuou 1b87d3f9a5 PartsEngine: Implement drag-and-drop processing
This implements Parts_SetDrag and drag/drop event handling. When a part
has drag enabled, clicking and moving the mouse will move the part and
emit DRAG_BEGIN/DRAGGING/DRAG_END messages. Drop target detection emits
DROP_ENTER/DROP_ON/DROPPED/DROP_LEAVE messages on parts under the cursor
during drag.
2026-04-25 07:38:46 +09:00
Nunuhara Cabbage 15f70f29a0 Merge pull request #328 from kichikuou/layoutbox
PartsEngine: Implement LayoutBox
2026-04-23 19:53:37 -07:00
kichikuou df6998c0f9 PartsEngine: Implement LayoutBox
This adds PARTS_LAYOUT_BOX parts type with support for automatic
horizontal/vertical child positioning. Layout calculation runs during
the component update cycle, repositioning children before their global
parameters are combined.
2026-04-22 11:25:28 +09:00
Nunuhara Cabbage 2f436ce5bf Merge pull request #327 from kichikuou/partsfunc
PartsEngine: Implement some PartsFuncs
2026-04-20 19:10:24 -07:00
kichikuou c4f89d040a PartsEngine: Implement PartsFunc 4 (SetWantSave)
This adds want_save parts flag which defaults to true. Parts with
want_save set to false are excluded from save data.
2026-04-20 09:03:43 +09:00
kichikuou 93c790dcc6 PartsEngine: Implement PartsFunc 159 (AddConstructProcess)
This function has a large number of parameters, which are serialized
fields from CASConstructionProcess. Dispatch on the command number to
the appropriate PE_Add*ToPartsConstructionProcess function.
2026-04-20 08:36:39 +09:00
kichikuou 88f199a8f2 PartsEngine: Implement PartsFunc 45 (PARTS_SetLockInputState)
This adds lock_input_state parts flag which prevents state transition.
2026-04-20 08:10:27 +09:00
kichikuou 28c8c277c9 PartsEngine: Implement PartsFunc 40-42 (PARTS_GetAbsolute[XYZ]) 2026-04-20 07:59:03 +09:00
kichikuou e5087f0757 PartsEngine: Implement PartsFunc 103 (GetPartsCGSurfaceArea) 2026-04-20 07:24:16 +09:00
Nunuhara Cabbage 6dc62bffc8 Merge pull request #326 from kichikuou/rect_detection
PartsEngine: Implement PE_SetPartsRectangleDetectionSize
2026-04-19 07:01:25 -07:00
Nunuhara Cabbage 0ea5d90797 Merge pull request #325 from kichikuou/parts_input
PartsEngine: Implement pass_cursor flag, MOUSE_ON and MOUSE_MOVE message dispatch
2026-04-19 07:01:05 -07:00
kichikuou d20f52cf79 PartsEngine: Implement PE_SetPartsRectangleDetectionSize
This sets the hitbox size for rectangle-based hit detection, as a new
parts type PARTS_RECT_DETECTION. This is a detection-only type with no
drawing.
2026-04-19 11:16:00 +09:00
kichikuou dcbd149409 PartsEngine: Implement MOUSE_ON and MOUSE_MOVE message dispatch
MOUSE_ON is sent every frame while hovering, with cursor position and
accumulated hover time. MOUSE_MOVE is sent when the cursor position
changes during hover.
2026-04-19 09:55:19 +09:00
kichikuou 1655c8a8e0 PartsEngine: Implement pass_cursor flag
This implements SetPassCursor/GetPartsPassCursor HLL functions. The
pass_cursor flag controls whether the cursor "passes through" a
component to reach parts behind it.

Key changes to input processing:
- Iteration order changed from back-to-front to front-to-back for proper
  cursor consumption
- MOUSE_ENTER / MOUSE_LEAVE now fire for all parts regardless of
  clickable
- Click hit test uses clickable || !pass_cursor pre-filter
- pass_cursor=false parts consume the cursor, preventing hover and
  click events from reaching parts behind them
2026-04-19 09:42:20 +09:00
Nunuhara Cabbage 2a99c75e4d Merge pull request #324 from kichikuou/setstate
PartsEngine: Fix state stuck at CLICKED when target state is uninitialized
2026-04-18 08:23:32 -07:00
Nunuhara Cabbage 70336546ed Merge pull request #323 from kichikuou/message
PartsEngine: Add message dispatch system
2026-04-18 08:23:09 -07:00
kichikuou cb24db29c2 PartsEngine: Fix state stuck at CLICKED when target state is uninitialized
parts_set_state silently ignored transitions to uninitialized states,
causing parts without a HOVERED state to remain in CLICKED after mouse
release. Fall back through CLICKED -> HOVERED -> DEFAULT so the first
initialized state is always selected. DEFAULT is accepted unconditionally
as the terminal fallback, matching the original engine's behavior.
2026-04-18 07:58:59 +09:00
kichikuou 03ef51e76a PartsEngine: Send mouse event messages
This introduces a per-parts is_hovered flag (replacing the global
parts_prev_pos) and fire MOUSE_ENTER, MOUSE_LEAVE, KEY_TRIGGER,
KEY_DOWN, MOUSE_CLICK, and KEY_UP events on the corresponding mouse
state transitions.

This also fixes the hittest in parts_update_mouse() to account for the
parent's global position, and to use the DEFAULT state hitbox (matching
the original engine's behavior).
2026-04-15 08:59:06 +09:00
kichikuou a0d78a024c PartsEngine: Implement message dispatch system
This adds a message queue that captures GUI events and exposes them
through HLL accessor functions.
2026-04-15 08:56:52 +09:00
Nunuhara Cabbage dc105aeb16 Fixes for Rance 9 English version
Add stubs for Discord HLL, and implement AFAFactory.LoadArchive.
2026-04-12 21:01:00 -07:00
Nunuhara Cabbage c877e261c5 Merge pull request #322 from kichikuou/ComponentType
PartsEngine: Implement SetComponentType and GetComponentType
2026-04-12 16:42:32 -07:00
Nunuhara Cabbage 39c0094ce8 Merge pull request #321 from kichikuou/controller
PartsEngine: Implement controller (layer) system
2026-04-12 16:42:06 -07:00
kichikuou 93ca13a50b PartsEngine: Implement SetComponentType and GetComponentType 2026-04-12 16:53:34 +09:00
kichikuou c540eb9796 PartsEngine: Implement controller (layer) system
PartsEngine in Rance 9 manages multiple "controllers" (called "layers"
in game scripts) that provide part grouping, draw-order control, and
bulk release. This implements the core controller API:

  - AddController / RemoveController: push/pop a controller on the
    stack. RemoveController releases all parts belonging to the removed
    controller and returns their IDs in erase_number_list.
  - SetActiveLayer / GetActiveLayer / GetSystemOverlayLayer: control
    which controller newly created parts are assigned to. The system
    overlay controller is a special controller that always renders on
    top and survives ReleaseAllWithoutSystem.
  - ReleaseAllWithoutSystem: releases all parts except those in the
    system overlay controller and re-initializes the controller stack.

Controllers are identified by their position in the stack (0 = bottom).
Draw ordering uses the scene's two-level sprite sorting: sp.z is set to
the controller's stack index and sp.z2 to the part's Z value, so parts
in higher controllers always render above lower ones regardless of
individual Z values. When the parts_multi_controller flag (see below) is
false, the original Z ordering is preserved (allowing Z-interleaving
with non-parts scene sprites -- see #180) for compatibility with games
that do not use controllers.

Save/load of the controller stack and per-part controller_no is gated on
a parts_multi_controller flag (set at PreLink time when the game's HLL
exports AddController) rather than a save version bump. This keeps the
save format stable for games that do not use controllers, while allowing
the save format to evolve freely during development.
2026-04-12 12:09:17 +09:00
Nunuhara Cabbage 753e202cea Merge pull request #320 from kichikuou/MainEXFile
MainEXFile fixes for Rance 9
2026-04-11 08:03:39 -07:00
Nunuhara Cabbage f884a58a77 Merge pull request #319 from kichikuou/movie
PartsEngine: Implement movie parts
2026-04-11 08:03:19 -07:00
kichikuou 2bc9a3d2cb MainEXFile: Fix Col() to accept EX_LIST
MainEXFile.Col() returns the number of items for EX_LIST handles.

Also removed the FIXME comments from Row and Col since the semantics
appear correct.
2026-04-10 14:29:20 +09:00
kichikuou 1e29930f4c MainEXFile: Fix set_indices/map_handles for EX_LIST
items->value was always indexing the first item; use items[i].value
to properly assign IDs and map handles for all list items.
2026-04-10 14:22:20 +09:00
kichikuou 887eebf31d MainEXFile: Add stub for ReloadDebugEXFile 2026-04-10 14:16:02 +09:00
kichikuou 96d46069ce PartsEngine: Implement movie parts
This adds PARTS_MOVIE type which creates a SACT sprite as a render
target for DrawMovie.

The GL texture is shared between the SACT sprite and parts_common, so
movie frames rendered to the sprite are directly visible through the
parts engine's rendering path.
2026-04-09 13:06:07 +09:00
kichikuou 9192bc80c5 Update libsys4 2026-04-09 12:59:52 +09:00
Nunuhara Cabbage 003ab16c72 Merge pull request #318 from kichikuou/kiwi
KiwiSoundEngine: Fix asset lookup for Rance 9
2026-04-08 17:27:24 -07:00
kichikuou 03062d5d61 KiwiSoundEngine: Fix asset lookup for Rance 9
In Rance 9 (and Blade Briders), KiwiSoundEngine looks up BGM and sound
files in archives by name formatted as "%05d" rather than by archive
index. Since the Sound_Seek function only exists in these games' HLL
definitions, its presence is used to switch Music_IsExist, Music_Prepare,
Sound_IsExist and Sound_Prepare to name-based lookups at PreLink time.
2026-04-08 08:52:51 +09:00
Nunuhara Cabbage 0bf5aaa8c8 Merge pull request #317 from kichikuou/flat
PartsEngine: Add initial support for Flat animation
2026-04-06 21:03:02 -07:00
kichikuou fe952354c5 PartsEngine: Add initial support for Flat animation
Only nested timelines and CG rendering are implemented. Stop motion and
particle emitters are not yet implemented.
2026-04-05 13:52:27 +09:00
Nunuhara Cabbage f43ca3a31a Merge pull request #316 from kichikuou/PartsEngine
Link PartsEngine.UpdateComponent to PartsEngine_Update()
2026-04-03 22:17:13 -07:00
Nunuhara Cabbage 52f2948e19 Merge pull request #315 from kichikuou/SealEngine
Add SealEngine HLL stub
2026-04-03 22:16:41 -07:00
kichikuou 92b2dd1990 Link PartsEngine.UpdateComponent to PartsEngine_Update()
PartsEngine.UpdateComponent() is a three-argument function that
corresponds to the older PartsEngine's Update() function.

PE_UpdateComponent is a one-argument function for
GUIEngine.UpdateComponent().
2026-04-04 09:51:11 +09:00
kichikuou f8ce39a324 Add SealEngine HLL stub
SealEngine is a successor to TapirEngine, the 3D engine used in Rance 9,
Evenicle, etc.
2026-04-04 07:50:58 +09:00
Nunuhara Cabbage 9562e0d1c3 Merge pull request #314 from kichikuou/PartsEngine
Initial PartsEngine work for Rance 9
2026-04-03 15:28:20 -07:00
kichikuou 623b3d3407 PartsEngine: Implement AddFillWithAlphaToPartsConstructionProcess 2026-04-02 13:21:34 +09:00
kichikuou 39886c30bd PartsEngine: Add stubs for some functions 2026-04-02 13:05:15 +09:00
kichikuou 1ad496c28d PartsEngine: Add HLL_(TODO_)EXPORTs for functions added in Rance 9
As noted in notes/parts-engine.md, many functions have been renamed.
They link to existing PE_ functions.

Some functions have had their arguments or return values changed from
int to float. For now, they just use the existing int functions.
2026-04-01 16:15:38 +09:00
Nunuhara Cabbage 039f1c9a2e Merge pull request #313 from kichikuou/CGManager
Implement CGManager functions for Rance 9
2026-03-31 21:36:04 -07:00
Nunuhara Cabbage caa593d00b Merge pull request #312 from kichikuou/dtor-order
Destruct struct members in reverse order of declaration
2026-03-31 21:35:38 -07:00
kichikuou 38610d93bb Implement CGManager functions for Rance 9 2026-03-30 16:54:36 +09:00
kichikuou 65fa898d9f Destruct struct members in reverse order of declaration
Class members are destroyed in the reverse order of their declaration
(like C++). The previous code destroyed members in declaration order,
which caused a use-after-free in Rance 9's T_Map class.

Added a test to verify the reverse destruction order.
2026-03-29 16:13:46 +09:00
Nunuhara Cabbage 547e8158a2 Merge pull request #311 from kichikuou/r9-delegate
Delegate fixes for Rance 9
2026-03-28 22:42:34 -07:00
kichikuou 197b89620c Fix extra heap_unref for ref arguments in delegate calls
When a delegate finishes calling all registered functions,
variable_fini() was called on the original stack arguments. For ref
types, this results in an unbalanced heap_unref() because the caller
pushes raw ref-values without incrementing the reference count. Non-ref
string/struct/array arguments are not affected because their push
instructions allocate new heap slots with ownership.

This fixes use-after-free in T_Loop@checkSelect in Rance 9.
2026-03-28 08:18:41 +09:00
kichikuou ecc04bc9bb Implement DG_NEW and DG_STR_TO_METHOD instructions
Used in Rance 9.
2026-03-28 08:18:41 +09:00
Nunuhara Cabbage 65ff73ca06 Merge pull request #310 from kichikuou/DrawMethod
ChipmunkSpriteEngine: Add draw method 2 (multiply) support
2026-03-21 08:19:44 -07:00
kichikuou 2ad0de6663 ChipmunkSpriteEngine: Add draw method 2 (multiply) support
This fixes a bug in Rance Quest where certain maps were not rendering.
2026-03-20 07:25:24 +09:00
Nunuhara Cabbage 5f318e859e Merge pull request #309 from kichikuou/pascha3-plus
Add support for Pascha3 Plus Contents
2026-03-14 08:19:12 -07:00
kichikuou efddc4f128 Update libsys4 2026-03-10 16:43:05 +09:00
kichikuou d93bb91d72 Add support for Pascha3 Plus Contents
Two changes from Pastel Chime 3 (likely to make the executables
incompatible):

- SystemService.XXX function
- PartsEngine.Update() has two dummy string parameters
2026-03-10 16:41:55 +09:00
Nunuhara Cabbage 49ee259d2c Merge pull request #307 from kichikuou/pascha3
Miscellaneous changes for Pastel Chime 3
2026-03-07 07:46:04 -08:00
kichikuou 3c362e9db6 game_compatibility.md: Mark Pastel Chime 3 as supported 2026-03-07 11:31:43 +09:00
kichikuou 1ad7cbb2c5 Fix array.Alloc(negative number)
array.Alloc(negative number) should create an empty array.

This fixes crash in C_HOSPITAL_ABILITY_SWITCH_PADDING::Create() in
Pastel Chime 3.
2026-03-07 11:31:43 +09:00
kichikuou a476b1d892 Implement KiwiSoundEngine.Sound_GetDataLength
Used in Pastel Chime 3.
2026-03-07 11:31:43 +09:00
kichikuou d4121032db system.ExistFile() should return false if path ends with a backslash
The system.ExistsFile() system call returns 1 if a file or directory
exists, but returns 0 when the provided path ends with a directory
separator, regardless of whether a directory exists at that location.

This subtle difference in behavior caused problems in Pastel Chime 3.
2026-03-07 11:31:43 +09:00
kichikuou 9474e321e1 Fix ChipmunkSpriteEngine.SP_SetDrawMethod
The meaning of the method parameter is the same as that of
StoatSpriteEngine.SP_SetDrawMethod.

This fixes a visual issue in Pastel Chime 3's battle scene.
2026-03-07 11:31:43 +09:00
Nunuhara Cabbage d130f38e74 Merge pull request #306 from kichikuou/dap
debugger_dap: Add null check to add_page_to_array()
2026-03-05 18:39:38 -08:00
kichikuou e66fe928e5 debugger_dap: Add null check to add_page_to_array()
This fixes a crash when inspecting an empty array with the `variables`
command.
2026-03-03 08:13:55 +09:00
Nunuhara Cabbage 134168d864 Merge pull request #305 from kichikuou/pascha3-parts
PartsEngine changes for Pastel Chime 3
2026-03-01 18:47:06 -08:00
kichikuou 6e755b21ef Update ChipmunkSpriteEngine to use the parts vertex shader
And set the `use_clipper` uniform to false.
2026-03-01 10:59:07 +09:00
kichikuou a5d488564a Implement PartsEngine.SetPartsAlphaClipperPartsNumber
This allows a specified part to be used as an alpha map (clipper) during
rendering. A dedicated vertex shader (parts.v.glsl) is introduced to
calculate clipping coordinates using an inverse transformation matrix.

Refactored parts_render_text() to use parts_render_texture() so that
alpha clipping is applied to text. This also enables color modifications
for text, while rotation and scaling remain unsupported, matching the
original PartsEngine behavior.

Incremented the parts save version since alpha_clipper_parts_no is now
persisted.
2026-03-01 07:47:40 +09:00
kichikuou 205e0a3ed0 Implement PartsEngine.ExistsFlashFile
Used in Pastel Chime 3.
2026-03-01 07:31:50 +09:00
kichikuou b758a04760 PartsEngine.SetPartsCG: Accept <save> path
This fixes an issue where thumbnails do not display on the save screen
in Pastel Chime 3.

If `cg_name` starting with "<save>" is passed, the function loads the CG
from the save directory.

Note that unlike GoatGUIEngine.SetPartsCG() in Rance Quest, `cg_name`
does not include "SaveData\\".
2026-03-01 07:31:50 +09:00
kichikuou 9c962e3467 Implement PartsEngine.AddGrayFilterToPartsConstructionProcess
It applies a grayscale filter to a specified area of the parts.
2026-03-01 07:31:36 +09:00
Nunuhara Cabbage 02129edc10 Merge pull request #304 from kichikuou/pascha3-hll
Implement HLLs for Pastel Chime 3
2026-02-28 08:17:58 -08:00
kichikuou 5ac79311b9 Implement P3SquareSprite HLL
This library is used to visualize unit movement area in the isometric
map.
2026-02-24 08:38:45 +09:00
kichikuou 75cfc9f18d Implement P3MapSprite HLL
This library is for rendering the isometric map grid in Pastel Chime 3.
It manages a background texture and an isometric grid texture. The grid
is generated from a sprite tile and an array representing tile
visibility. It provides functions to create, delete, and copy/blend
these textures onto destination sprites.
2026-02-24 08:38:45 +09:00
kichikuou eaa7f3d8d0 Implement MADLoader HLL
This HLL provides functionality for loading, managing, and rendering
animations stored in the MAD file format. A MAD file consists of a
header followed by a series of QNT image frames.
2026-02-24 08:38:45 +09:00
Nunuhara Cabbage 0d7ef9b577 Merge pull request #303 from brombrom-hani/system-service-rance-9
SystemService: Function in rance 9
2026-02-23 06:16:22 -08:00
Nunuhara Cabbage 4a7fc4d12a Merge pull request #302 from kichikuou/group-save
Treat invalid global group name as empty group
2026-02-23 06:16:06 -08:00
BromBrom-Hani e662955a1d SystemService: Function in rance 9 2026-02-21 18:37:34 +01:00
kichikuou a3c23387f4 Treat invalid global group name as empty group
Pastel Chime 3 executes system.GroupSave() with a group name of
"共有データ", but that group name does not exist in the OBJG section of
AIN.

Pascha3.exe does not treat such system.GroupSave() calls as failures,
instead behaving as if saving an empty variable group.
2026-02-21 11:02:39 +09:00
Nunuhara Cabbage f23125cb77 Merge pull request #301 from kichikuou/sort-strarray
Fix use-after-free in custom string array sort
2026-02-16 20:35:02 -08:00
kichikuou 30ac11e74b Fix use-after-free in custom string array sort
Custom comparison functions for string arrays receive their arguments by
value. Create a copy of the strings before pushing them to the stack to
prevent them from being prematurely freed after the function call.
2026-02-16 08:49:33 +09:00
Nunuhara Cabbage f40132497c Merge pull request #300 from kichikuou/pascha3
Initial HLL work for Pastel Chime 3
2026-02-14 17:47:16 -08:00
kichikuou 7540712de1 Add stub for PartsEngine.SetPartsAlphaClipperPartsNumber
For Pastel Chime 3.
2026-02-15 09:35:24 +09:00
kichikuou 6a75bc5a6b flash: reset playback state in PE_BackPartsFlashBeginFrame
This fixes a bug in Pastel Chime 3 where message progression would hang
because the Flash animation failed to resume playback after being reset.
2026-02-15 09:12:03 +09:00
kichikuou a1a809faef VSFile: Support relative paths from the game directory
Relative paths are used in Pastel Chime 3.
2026-02-14 12:41:04 +09:00
kichikuou 3929de81f1 Add stubs for AFAFactory hll 2026-02-14 12:41:01 +09:00
kichikuou 65ccb29423 Add ValueEncryption HLL
This appears to be an anti-cheat mechanism. To make save files
compatible, it should do the same calculation as the original,
but for now we just return the values as they are.
2026-02-14 12:36:58 +09:00
Nunuhara Cabbage 9f3af7b256 Merge pull request #299 from kichikuou/fnl
fnl: Fix glyph width handling
2026-02-07 08:23:45 -08:00
Nunuhara Cabbage 249bc06f53 Merge pull request #298 from kichikuou/xfree_aligned
Fix crash on Windows introduced in commit ed2239f
2026-02-07 08:22:18 -08:00
kichikuou 621eb49be8 fnl: Fix glyph width handling
This fixes the row stride calcuration for the second font of Pastel
Chime 3.
2026-02-06 16:47:15 +09:00
kichikuou 5f13b3e9d0 Fix crash on Windows introduced in commit ed2239f
Memory allocated with xcalloc_aligned (which uses _aligned_malloc on
Windows) must be freed with _aligned_free. Using standard free() caused
heap corruption and crashes.
2026-02-06 16:29:14 +09:00
Nunuhara Cabbage 15cfc5463b Merge pull request #296 from kichikuou/alignment-fix
Use xcalloc_aligned for allocating structures containing mat4
2026-01-12 17:08:34 -08:00
kichikuou ed2239fb1b Use xcalloc_aligned for allocating structures containing mat4
This fixes #294.

cglm's mat4 requires 32-byte alignment when __AVX__ is defined.
2026-01-13 08:17:31 +09:00
kichikuou 5980e49a96 Update libsys4
There is no need to call initialize_instructions() in system4.c, because
ain_open() initializes the instruction set internally when it reads the
VERS tag.
2026-01-13 08:11:33 +09:00
Nunuhara Cabbage a6871623f2 flatpak: include metadata file 2025-11-15 15:14:15 -08:00
Nunuhara Cabbage 477bbdb949 ci: cache FFmpeg build 2025-11-15 14:03:13 -08:00
Nunuhara Cabbage 0baa493f79 flatpak: enable --device=input permission
For gamepad access.
2025-11-15 13:02:40 -08:00
Nunuhara Cabbage 2c51bec001 Merge pull request #293 from brombrom-hani/fullscreen-toggle
SystemService: fullscreen control
2025-11-15 12:59:13 -08:00
BromBrom d6b5bde80c SystemService: fullscreen control 2025-11-15 13:42:20 +01:00
Nunuhara Cabbage 2510fa6b9b ci: add nightly build 2025-11-14 16:50:45 -08:00
Nunuhara Cabbage 8fecf72281 Merge pull request #292 from kichikuou/flash-fix
parts: Fix ERROR() in flash.c
2025-10-31 22:03:19 -07:00
kichikuou 93ce40bcdd parts: Fix ERROR() in flash.c
This fixes a crash when processing DefineSprite tags that contain no
PlaceObject2 tags, as seen in Rance Quest's `Battle/斧攻撃・雷.aff`.
These sprites are now registered in parts_flash->sprites with a NULL
object, and are safely skipped by the renderer.
2025-10-30 09:48:22 +09:00
Nunuhara Cabbage 4f2d367b19 Fix bug _ald_get
See description of bug at nunuhara/libsys4@4da6bf6

This fixes an issue in Rance VI when compiled at high optimization
levels.
2025-10-21 20:50:26 -07:00
Nunuhara Cabbage 74bdbd4f37 Add windows build workflow 2025-10-21 18:58:39 -07:00
Nunuhara Cabbage 2c8fc425db Add linux CI workflow 2025-10-21 17:53:39 -07:00
Nunuhara Cabbage f59158648f Check in test .ain file 2025-10-21 17:42:52 -07:00
Nunuhara Cabbage 484b643f02 Merge pull request #291 from kichikuou/android-manual
Add Android support for SystemService.OpenPlayingManual
2025-10-18 06:48:18 -07:00
kichikuou d1fdab4cde Add Android support for SystemService.OpenPlayingManual
The heavy lifting will be done on the Java side. Here we just send a
message.
2025-10-18 13:43:10 +09:00
Nunuhara Cabbage 43c6a2804c Merge pull request #290 from kichikuou/dap-race
debugger_dap: Defer starting execution until configuration is done
2025-10-10 21:49:35 -07:00
kichikuou 180c81e5f5 debugger_dap: Defer starting execution until configuration is done
The debugger previously started execution upon receiving a 'launch'
request. This created a race condition where execution could begin
before breakpoints were set by the client.

This change ensures the debugger waits for both 'launch' and
'configurationDone' requests before starting the debugee. This allows
the client to set breakpoints before execution begins.
2025-10-11 11:20:35 +09:00
Nunuhara Cabbage e29739f0be Merge pull request #289 from kichikuou/dap-output
debugger_dap: Fix garbled characters in output events on Windows
2025-10-10 18:42:38 -07:00
kichikuou 1e682bad09 debugger_dap: Fix garbled characters in output events on Windows 2025-10-11 09:49:16 +09:00
Nunuhara Cabbage 0a4981533b Merge pull request #288 from kichikuou/build-fix
Fix build error when debugger is disabled
2025-10-05 07:48:16 -07:00
kichikuou 998ab23dab Fix build error when debugger is disabled
This fixes "call to undeclared function 'screenshot_save'" error when
xsystem4.h is not included by debugger.h.
2025-10-05 18:54:14 +09:00
Nunuhara Cabbage dc8342cbab Merge pull request #287 from kichikuou/android-fullscreen
Android: Do not use SDL_WINDOW_FULLSCREEN
2025-10-04 22:30:48 -07:00
kichikuou 8d9eb931f6 Redraw screen on SDL_APP_DIDENTERFOREGROUND
Without this, the screen remains blank after resuming the app on
Android.
2025-10-04 16:06:36 +09:00
kichikuou 1621378ba1 Android: Create window with SDL_WINDOW_RESIZABLE
This reverts commit b7ab1ed476
("Android: Enforce landscape fullscreen mode").

To work around https://github.com/libsdl-org/SDL/issues/8995 where the
system bar does not disappear after resuming the app, create the window
in non-fullscreen mode. Also, since SDL hints can be configured in
AndroidManifest.xml, there is no need to set SDL_HINT_ORIENTATIONS here.
2025-10-04 15:35:30 +09:00
111 changed files with 11167 additions and 922 deletions
+194
View File
@@ -0,0 +1,194 @@
name: Build
on: [push, pull_request]
jobs:
linux-build:
runs-on: ubuntu-latest
strategy:
matrix:
build-type: ["debug", "release"]
name: Linux ${{ matrix.build-type }}
steps:
- name: Checkout Source
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install Dependencies
run: |
sudo apt update
sudo apt install flex bison meson libfreetype-dev libglew-dev libffi-dev libpng-dev libsndfile-dev libturbojpeg0-dev libwebp-dev libsdl2-dev zlib1g-dev
- name: Build
run: |
mkdir -p out/${{ matrix.build-type }}
meson -Dbuildtype=${{ matrix.build-type }} out/${{ matrix.build-type }}
ninja -C out/${{ matrix.build-type }}
- name: Test
run: out/${{ matrix.build-type }}/src/xsystem4 test/Run/test.ain
flatpak-build:
name: Flatpak
runs-on: ubuntu-latest
container:
image: ghcr.io/flathub-infra/flatpak-github-actions:gnome-48
options: --privileged
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: flatpak/flatpak-github-actions/flatpak-builder@v6
with:
bundle: xsystem4-linux-x86_64-nightly.flatpak
manifest-path: technology.haniwa.xsystem4.yml
cache-key: flatpak-builder-${{ github.sha }}
windows-build:
runs-on: windows-latest
name: Windows
defaults:
run:
shell: msys2 {0}
steps:
- name: Checkout Source
uses: actions/checkout@v4
with:
submodules: recursive
- name: Setup MSYS2
uses: msys2/setup-msys2@v2
with:
update: true
install: >-
base-devel
git
nasm
diffutils
zip
mingw-w64-x86_64-gcc
mingw-w64-x86_64-meson
mingw-w64-x86_64-pkg-config
mingw-w64-x86_64-SDL2
mingw-w64-x86_64-freetype
mingw-w64-x86_64-libjpeg-turbo
mingw-w64-x86_64-libwebp
mingw-w64-x86_64-libpng
mingw-w64-x86_64-libsndfile
mingw-w64-x86_64-glew
- name: Cache FFmpeg
id: cache-ffmpeg
uses: actions/cache@v4
with:
path: FFmpeg/destdir
key: ${{ runner.os }}-ffmpeg-n7.1.2
- name: Build FFmpeg
if: ${{ steps.cache-ffmpeg.outputs.cache-hit != 'true' }}
run: |
git clone --depth 1 --branch n7.1.2 https://github.com/FFmpeg/FFmpeg.git
cd FFmpeg
./configure --disable-everything \
--enable-decoder=mpegvideo \
--enable-decoder=mpeg1video \
--enable-decoder=mpeg2video \
--enable-decoder=mp2 \
--enable-parser=mpegaudio \
--enable-parser=mpegvideo \
--enable-demuxer=mpegps \
--enable-demuxer=mpegts \
--enable-demuxer=mpegtsraw \
--enable-demuxer=mpegvideo \
--enable-decoder=vc1 \
--enable-decoder=wmapro \
--enable-parser=vc1 \
--enable-hwaccel=vc1_d3d11va \
--enable-hwaccel=vc1_d3d11va2 \
--enable-hwaccel=vc1_dxva2 \
--enable-demuxer=asf \
--enable-protocol=file \
--enable-filter=scale \
--enable-static \
--disable-shared \
--extra-libs=-static \
--extra-cflags=--static \
--prefix=/mingw64
make -j$(nproc)
mkdir destdir
DESTDIR=destdir make install
cd ..
- name: Install FFmpeg
run: |
cp -a FFmpeg/destdir/. /
- name: Build
run: |
mkdir debug
meson debug -Dbuildtype=debug
ninja -C debug
mkdir release
meson release -Dbuildtype=release
ninja -C release
- name: Test
run: |
debug/src/xsystem4.exe test/Run/test.ain
release/src/xsystem4.exe test/Run/test.ain
- name: Copy licenses
run: |
ls /mingw64/share/licenses
mkdir dist
cp -r /mingw64/share/licenses dist/
- name: Package
run: |
cp release/src/xsystem4.exe COPYING README.md game_compatibility.md dist/
cp debug/src/xsystem4.exe dist/xsystem4-debug.exe
cp /mingw64/bin/glew32.dll dist/
cp extra/*.lnk dist/
cp -r fonts dist/
cp -r shaders dist/
mv dist xsystem4
zip -r -9 xsystem4-windows-x86_64-nightly.zip xsystem4
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: xsystem4
path: xsystem4-windows-x86_64-nightly.zip
nightly:
name: Update nightly release
needs: [windows-build, flatpak-build]
if: ${{ github.repository == 'nunuhara/xsystem4' && github.ref == 'refs/heads/master' }}
runs-on: ubuntu-latest
steps:
- name: Download release artifacts
uses: actions/download-artifact@v5
with:
merge-multiple: true
- name: Update nightly tag
uses: richardsimko/update-tag@v1
with:
tag_name: nightly
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Release nightly
uses: softprops/action-gh-release@v2
with:
prerelease: true
name: nightly
tag_name: nightly
files: |
xsystem4-windows-x86_64-nightly.zip
xsystem4-linux-x86_64-nightly.flatpak
fail_on_unmatched_files: true
+12
View File
@@ -79,10 +79,13 @@ target_sources(xsystem4 PRIVATE
src/3d/collision.c
src/3d/debug.c
src/3d/model.c
src/3d/mpr.c
src/3d/parser.c
src/3d/particle.c
src/3d/reign.c
src/3d/renderer.c
src/3d/s3de.c
src/3d/save.c
src/dungeon/dgn.c
src/dungeon/dtx.c
@@ -98,7 +101,10 @@ target_sources(xsystem4 PRIVATE
src/parts/construction.c
src/parts/debug.c
src/parts/flash.c
src/parts/flat.c
src/parts/input.c
src/parts/layoutbox.c
src/parts/message.c
src/parts/motion.c
src/parts/parts.c
src/parts/render.c
@@ -107,6 +113,7 @@ target_sources(xsystem4 PRIVATE
src/hll/ACXLoader.c
src/hll/ADVSYS.c
src/hll/AFAFactory.c
src/hll/AliceLogo.c
src/hll/AliceLogo2.c
src/hll/AliceLogo3.c
@@ -131,6 +138,7 @@ target_sources(xsystem4 PRIVATE
src/hll/DALKEDemo.c
src/hll/Data.c
src/hll/DataFile.c
src/hll/Discord.c
src/hll/DrawDungeon.c
src/hll/DrawEffect.c
src/hll/DrawGraph.c
@@ -155,6 +163,7 @@ target_sources(xsystem4 PRIVATE
src/hll/InputString.c
src/hll/KiwiSoundEngine.c
src/hll/LoadCG.c
src/hll/MADLoader.c
src/hll/MainEXFile.c
src/hll/MainSurface.c
src/hll/MamanyoDemo.c
@@ -170,6 +179,8 @@ target_sources(xsystem4 PRIVATE
src/hll/MusicSystem.c
src/hll/NewFont.c
src/hll/OutputLog.c
src/hll/P3MapSprite.c
src/hll/P3SquareSprite.c
src/hll/PassRegister.c
src/hll/PastelChime2.c
src/hll/PartsEngine.c
@@ -211,6 +222,7 @@ target_sources(xsystem4 PRIVATE
src/hll/vmSurface.c
src/hll/vmSystem.c
src/hll/vmTimer.c
src/hll/ValueEncryption.c
src/hll/VSFile.c
)
Binary file not shown.
+15 -2
View File
@@ -77,8 +77,21 @@ issues, please let me know or open a pull request to update this table.
| ランス・クエスト | Supported | |
| ランス・クエスト マグナム | Supported | |
| 母娘乱館 | Unknown | |
| パステルチャイム3 バインドシーカー | Unsupported | |
| パステルチャイム3 バインドシーカー | Supported | |
| パスチャ3 プラスコンテンツ | Supported | |
| どらぺこ! ~おねだりドラゴンとおっぱい勇者~ | Unsupported | |
| ランス01 光をもとめて | Supported | |
| Rance 01 - Quest for Hikari (EN) | Supported | |
| All games released after Rance 01 | Unsupported | |
| ランスIX-ヘルマン革命- | Supported | |
| Rance IX - The Helmanian Revolution (EN) | Unknown | |
| 武想少女隊ぶれいど☆ブライダーズ | Supported | |
| イブニクル | Unsupported | |
| Evenicle (EN) | Unsupported | |
| ランス03 リーザス陥落 | Unsupported | |
| Rance 03 -The Fall of Leazas- (EN) | Unsupported | |
| 妻みぐい3 | Unsupported | |
| は~とふるママン | Unsupported | |
| 超昂神騎エクシール | Unsupported | |
| Beat Valkyrie Ixseal (EN) | Unsupported | |
| Rance X -決戦- | Unsupported | |
| All games released after Rance X | Unsupported | |
+6 -1
View File
@@ -19,6 +19,8 @@
#include <stdbool.h>
#define AUDIO_NO_METADATA -1
struct archive_data;
void audio_init(void);
@@ -78,6 +80,8 @@ int wav_get_volume(int id);
int bgm_get_volume(int id);
int wav_get_time_length(int id);
int bgm_get_time_length(int id);
int wav_get_data_length(int no);
int bgm_get_data_length(int no);
int wav_get_group_num(int id);
//int bgm_get_group_num(int id);
int wav_prepare_from_file(int id, char *filename);
@@ -86,6 +90,7 @@ int bgm_prepare_from_file(int id, char *filename);
int wav_get_group_num_from_data_num(int no);
// Takes ownership of dfile.
int wav_prepare_from_archive_data(int id, struct archive_data *dfile);
int wav_prepare_from_archive_data(int id, int metadata_no, struct archive_data *dfile);
int bgm_prepare_from_archive_data(int id, int metadata_no, struct archive_data *dfile);
#endif /* SYSTEM4_AUDIO_H */
+1
View File
@@ -72,6 +72,7 @@ enum effect {
EFFECT_BLUR_FADEOUT = 50,
EFFECT_BLUR_CROSSFADE = 51,
EFFECT_2ROT_ZOOM_BLEND_BLUR = 52,
EFFECT_VWAVE_SCROLL_CROSSFADE = 55,
EFFECT_VWAVE_CROSSFADE = 60,
NR_EFFECTS
};
+2 -8
View File
@@ -163,21 +163,15 @@ float gfx_render_textf(Texture *dst, float x, int y, char *msg, struct text_styl
void gfx_render_dash_text(Texture *dst, struct text_style *ts);
void gfx_draw_text_to_amap(Texture *dst, int x, int y, char *text);
void gfx_draw_text_to_pmap(Texture *dst, int x, int y, char *text);
float gfx_size_char_unstyled(struct text_style *ts, const char *ch);
float gfx_size_char(struct text_style *ts, const char *ch);
float gfx_size_char_kerning(struct text_style *ts, uint32_t code, uint32_t code_next);
float gfx_size_text(struct text_style *ts, const char *text);
float gfx_get_actual_font_size(unsigned face, float size);
float gfx_get_actual_font_size_round_down(unsigned face, float size);
static inline float text_style_width(struct text_style *ts, const char *ch)
{
return (gfx_size_char(ts, ch) + (ts->bold_width * 2) + ts->edge_left + ts->edge_right)
* ts->scale_x;
}
static inline float text_style_height(struct text_style *ts)
{
return ts->size + (ts->bold_width * 2) + ts->edge_up + ts->edge_down;
return ts->size + ceilf(ts->bold_width)*2 + ts->edge_up + ts->edge_down;
}
#endif /* SYSTEM4_FONT_H */
+5
View File
@@ -77,6 +77,9 @@ void gfx_fini(void);
Texture *gfx_main_surface(void);
void gfx_set_window_logical_size(int w, int h);
bool gfx_set_fullscreen(bool enable);
bool gfx_is_fullscreen(void);
void gfx_toggle_fullscreen(void);
void gfx_update_screen_scale(void);
void gfx_set_wait_vsync(bool wait);
float gfx_get_frame_rate(void);
@@ -181,6 +184,8 @@ void gfx_copy_with_alpha_map(Texture *dst, int dx, int dy, Texture *src, int sx,
void gfx_fill_with_alpha(Texture *dst, int x, int y, int w, int h, int r, int g, int b, int a);
void gfx_copy_stretch_with_alpha_map(Texture *dst, int dx, int dy, int dw, int dh, Texture *src, int sx, int sy, int sw, int sh);
void gfx_copy_grayscale(Texture *dst, int dx, int dy, Texture *src, int sx, int sy, int w, int h);
void gfx_copy_grayscale_with_alpha_map(Texture *dst, int dx, int dy, Texture *src, int sx, int sy, int w, int h);
void gfx_copy_grayscale_reverse_LR_with_alpha_map(Texture *dst, int dx, int dy, Texture *src, int sx, int sy, int w, int h);
void gfx_draw_line(Texture *dst, int x0, int y0, int x1, int y1, int r, int g, int b);
void gfx_draw_line_to_amap(Texture *dst, int x0, int y0, int x1, int y1, int a);
void gfx_draw_glyph(Texture *dst, float dx, int dy, Texture *glyph, SDL_Color color, float scale_x, float bold_width, bool blend);
+2
View File
@@ -153,5 +153,7 @@ void register_input_handler(void(*handler)(const char*));
void clear_input_handler(void);
void register_editing_handler(void(*handler)(const char*, int, int));
void clear_editing_handler(void);
void register_key_handler(void(*handler)(int));
void clear_key_handler(void);
#endif /* SYSTEM4_INPUT_H */
+3 -2
View File
@@ -59,8 +59,9 @@ struct channel;
enum asset_type;
struct channel *channel_open(enum asset_type type, int no);
// Takes ownership of dfile.
struct channel *channel_open_archive_data(struct archive_data *dfile);
// Takes ownership of dfile. Metadata is not applied if metadata_no is negative.
struct channel *channel_open_archive_data(struct archive_data *dfile,
enum asset_type type, int metadata_no);
struct channel *channel_open_file(const char *path);
void channel_close(struct channel *ch);
int channel_play(struct channel *ch);
+81
View File
@@ -23,6 +23,7 @@ struct page;
struct string;
// parts.c
void PE_enable_multi_controller(void);
bool PE_Init(void);
void PE_Reset(void);
void PE_Update(int passed_time, bool message_window_show);
@@ -36,6 +37,7 @@ bool PE_SetPartsCG_by_string_index(int parts_no, struct string *cg_no,
int sprite_deform, int state);
void PE_GetPartsCGName(int parts_no, struct string **cg_name, int state);
bool PE_SetPartsCGSurfaceArea(int parts_no, int x, int y, int w, int h, int state);
void PE_GetPartsCGSurfaceArea(int parts_no, int *x, int *y, int *w, int *h, int state);
int PE_GetPartsCGNumber(int parts_no, int state);
bool PE_SetLoopCG_by_index(int parts_no, int cg_no, int nr_frames, int frame_time, int state);
bool PE_SetLoopCG(int parts_no, struct string *cg_name, int start_no, int nr_frames,
@@ -67,6 +69,7 @@ bool PE_SetNumeralSurfaceArea(int parts_no, int x, int y, int w, int h, int stat
void PE_ReleaseParts(int parts_no);
void PE_ReleaseAllParts(void);
void PE_ReleaseAllPartsWithoutSystem(void);
void PE_ReleaseAllWithoutSystem(struct page **erase_number_list);
void PE_SetPos(int parts_no, int x, int y);
int PE_GetPartsX(int parts_no);
int PE_GetPartsY(int parts_no);
@@ -94,6 +97,7 @@ bool PE_SetPartsGroupNumber(int parts_no, int group_no);
void PE_SetPartsMessageWindowShowLink(int parts_no, bool message_window_show_link);
bool PE_GetPartsMessageWindowShowLink(int parts_no);
void PE_SetSpeedupRateByMessageSkip(int parts_no, int rate);
int PE_GetSpeedupRateByMessageSkip(int parts_no);
void PE_SetPartsMagX(int parts_no, float scale_x);
float PE_GetPartsMagX(int parts_no);
void PE_SetPartsMagY(int parts_no, float scale_y);
@@ -102,9 +106,12 @@ void PE_SetPartsRotateX(int parts_no, float rot_x);
void PE_SetPartsRotateY(int parts_no, float rot_y);
void PE_SetPartsRotateZ(int parts_no, float rot_z);
float PE_GetPartsRotateZ(int parts_no);
void PE_SetPartsAlphaClipperPartsNumber(int parts_no, int alpha_clipper_parts_no);
void PE_SetPartsPixelDecide(int PartsNumber, bool pixel_decide);
bool PE_SetThumbnailReductionSize(int reduction_size);
bool PE_SetThumbnailMode(bool Mode);
void PE_SetComponentType(int parts_no, int type, int state);
int PE_GetComponentType(int parts_no, int state);
void PE_SetInputState(int parts_no, int state);
int PE_GetInputState(int parts_no);
bool PE_SetPartsRectangleDetectionSize(int parts_no, int w, int h, int state);
@@ -112,9 +119,27 @@ bool PE_SetPartsCGDetectionSize(int parts_no, struct string *cg_name, int state)
bool PE_Save(struct page **buffer);
bool PE_SaveWithoutHideParts(struct page **buffer);
bool PE_Load(struct page **buffer);
int PE_AddController(int index);
void PE_RemoveController(struct page **erase_number_list, int index);
bool PE_CreateParts3DLayerPluginID(int parts_no, int state);
int PE_GetParts3DLayerPluginID(int parts_no, int state);
bool PE_ReleaseParts3DLayerPluginID(int parts_no, int state);
// GUIEngine
int PE_GetFreeNumber(void);
bool PE_IsExist(int parts_no);
// PartsFunc interface
void PE_set_active_controller(int controller_no);
int PE_get_active_controller(void);
int PE_get_system_controller(void);
int PE_get_nr_controllers(void);
void PE_parts_set_want_save(int parts_no, bool want_save);
bool PE_save_thumbnail(struct string *filename, int reduction_factor);
bool PE_init_parts_movie(int parts_no, int width, int height, int bg_r, int bg_g, int bg_b, int state);
int PE_get_movie_sprite(int parts_no, int state);
float PE_parts_get_absolute_x(int parts_no);
float PE_parts_get_absolute_y(int parts_no);
int PE_parts_get_absolute_z(int parts_no);
void PE_parts_set_lock_input_state(int parts_no, bool lock);
// construction.c
bool PE_AddCreateToPartsConstructionProcess(int parts_no, int w, int h, int state);
@@ -125,12 +150,16 @@ bool PE_AddFillToPartsConstructionProcess(int parts_no, int x, int y, int w, int
bool PE_AddFillAlphaColorToPartsConstructionProcess(int parts_no, int x, int y, int w, int h,
int r, int g, int b, int a, int state);
bool PE_AddFillAMapToPartsConstructionProcess(int parts_no, int x, int y, int w, int h, int a, int state);
bool PE_AddFillWithAlphaToPartsConstructionProcess(int parts_no, int x, int y, int w, int h,
int r, int g, int b, int a, int state);
bool PE_AddDrawRectToPartsConstructionProcess(int parts_no, int x, int y, int w, int h,
int r, int g, int b, int state);
bool PE_AddDrawCutCGToPartsConstructionProcess(int parts_no, struct string *cg_name,
int dx, int dy, int dw, int dh, int sx, int sy, int sw, int sh, int interp_type, int state);
bool PE_AddCopyCutCGToPartsConstructionProcess(int parts_no, struct string *cg_name,
int dx, int dy, int dw, int dh, int sx, int sy, int sw, int sh, int interp_type, int state);
bool PE_AddGrayFilterToPartsConstructionProcess(int parts_no, int x, int y, int w, int h,
bool full_size, int state);
bool PE_AddCopyTextToPartsConstructionProcess(int parts_no, int x, int y, struct string *text,
int type, int size, int r, int g, int b, float bold_weight,
int edge_r, int edge_g, int edge_b, float edge_weight,
@@ -145,8 +174,11 @@ bool PE_SetPartsConstructionSurfaceArea(int parts_no, int x, int y, int w, int h
// input.c
void PE_UpdateInputState(int passed_time);
void PE_SetPassCursor(int parts_no, bool pass);
bool PE_GetPartsPassCursor(int parts_no);
void PE_SetClickable(int parts_no, bool clickable);
bool PE_GetPartsClickable(int parts_no);
void PE_SetDrag(int parts_no, bool enable);
void PE_SetPartsGroupDecideOnCursor(int group_no, bool decide_on_cursor);
void PE_SetPartsGroupDecideClick(int group_no, bool decide_click);
void PE_SetOnCursorShowLinkPartsNumber(int parts_no, int link_parts_no);
@@ -159,6 +191,19 @@ void PE_EndInput(void);
int PE_GetClickPartsNumber(void);
bool PE_IsCursorIn(int parts_no, int mouse_x, int mouse_y, int state);
// message.c
void PE_ReleaseMessage(void);
void PE_PopMessage(void);
int PE_GetMessageType(void);
int PE_GetMessagePartsNumber(void);
int PE_GetMessageDelegateIndex(void);
int PE_GetMessageVariableCount(void);
int PE_GetMessageVariableType(int index);
int PE_GetMessageVariableInt(int index);
float PE_GetMessageVariableFloat(int index);
bool PE_GetMessageVariableBool(int index);
void PE_GetMessageVariableString(int index, struct string **out);
// motion.c
void PE_AddMotionPos(int parts_no, int begin_x, int begin_y, int end_x, int end_y, int begin_t, int end_t);
void PE_AddMotionPos_curve(int parts_no, int begin_x, int begin_y, int end_x, int end_y,
@@ -205,9 +250,11 @@ void PE_SeekEndMotion(void);
void PE_UpdateMotionTime(int time, bool skip);
bool PE_IsMotion(void);
int PE_GetMotionEndTime(void);
void PE_PauseMotion(bool pause);
// text.c
bool PE_SetText(int parts_no, struct string *text, int state);
struct string *PE_GetText(int parts_no, int state);
bool PE_AddPartsText(int parts_no, struct string *text, int state);
bool PE_SetPartsTextSurfaceArea(int parts_no, int x, int y, int w, int h, int state);
bool PE_SetFont(int parts_no, int type, int size, int r, int g, int b, float bold_weight, int edge_r, int edge_g, int edge_b, float edge_weight, int state);
@@ -221,6 +268,7 @@ bool PE_SetTextCharSpace(int parts_no, int char_space, int state);
bool PE_SetTextLineSpace(int parts_no, int line_space, int state);
// flash.c
bool PE_ExistsFlashFile(struct string *flash_filename);
bool PE_SetPartsFlash(int parts_no, struct string *flash_filename, int state);
bool PE_IsPartsFlashEnd(int parts_no, int state);
int PE_GetPartsFlashCurrentFrameNumber(int parts_no, int state);
@@ -232,4 +280,37 @@ bool PE_StartPartsFlash(int parts_no, int state);
bool PE_GoFramePartsFlash(int parts_no, int frame_no, int state);
int PE_GetPartsFlashEndFrame(int parts_no, int state);
// flat.c
bool PE_ExistsFlatFile(struct string *filename);
bool PE_SetPartsFlat(int parts_no, struct string *filename, int state);
bool PE_IsPartsFlatEnd(int parts_no, int state);
int PE_GetPartsFlatCurrentFrameNumber(int parts_no, int state);
bool PE_BackPartsFlatBeginFrame(int parts_no, int state);
bool PE_StepPartsFlatFinalFrame(int parts_no, int state);
bool PE_SetPartsFlatSurfaceArea(int parts_no, int x, int y, int w, int h, int state);
bool PE_SetPartsFlatAndStop(int parts_no, struct string *filename, int state);
bool PE_StopPartsFlat(int parts_no, int state);
bool PE_StartPartsFlat(int parts_no, int state);
bool PE_GoFramePartsFlat(int parts_no, int frame_no, int state);
int PE_GetPartsFlatEndFrame(int parts_no, int state);
// layoutbox.c
void PE_SetLayoutBoxLayoutType(int parts_no, int type);
int PE_GetLayoutBoxLayoutType(int parts_no);
void PE_SetLayoutBoxReturn(int parts_no, bool return_flag, int return_size);
bool PE_IsLayoutBoxReturn(int parts_no);
int PE_GetLayoutBoxReturnSize(int parts_no);
void PE_SetLayoutBoxAlign(int parts_no, int align);
int PE_GetLayoutBoxAlign(int parts_no);
void PE_SetComponentMargin(int parts_no, int top, int bottom, int left, int right);
int PE_GetComponentMarginTop(int parts_no);
int PE_GetComponentMarginBottom(int parts_no);
int PE_GetComponentMarginLeft(int parts_no);
int PE_GetComponentMarginRight(int parts_no);
void PE_set_layoutbox_padding(int parts_no, int top, int bottom, int left, int right);
int PE_get_layoutbox_padding_top(int parts_no);
int PE_get_layoutbox_padding_bottom(int parts_no);
int PE_get_layoutbox_padding_left(int parts_no);
int PE_get_layoutbox_padding_right(int parts_no);
#endif /* SYSTEM4_PARTS_H */
+106 -10
View File
@@ -1669,6 +1669,9 @@ uint16_t plm_buffer_read_vlc_uint(plm_buffer_t *self, const plm_vlc_uint_t *tabl
// ----------------------------------------------------------------------------
// plm_demux implementation
//
// xsystem4 change: The demuxer accepts MPEG-1 and MPEG-2 Systems pack/PES headers.
// The video and audio decoders still only support MPEG-1 Video and MP2 Audio.
static const int PLM_START_PACK = 0xBA;
static const int PLM_START_END = 0xB9;
@@ -1685,6 +1688,7 @@ struct plm_demux_t {
double duration;
int start_code;
int is_mpeg2;
int has_pack_header;
int has_system_header;
int has_headers;
@@ -1738,19 +1742,52 @@ int plm_demux_has_headers(plm_demux_t *self) {
}
self->start_code = PLM_START_PACK;
if (!plm_buffer_has(self->buffer, 64)) {
return FALSE;
}
self->start_code = -1;
if (plm_buffer_read(self->buffer, 4) != 0x02) {
if (!plm_buffer_has(self->buffer, 8)) {
return FALSE;
}
self->system_clock_ref = plm_demux_decode_time(self);
plm_buffer_skip(self->buffer, 1);
plm_buffer_skip(self->buffer, 22); // mux_rate * 50
plm_buffer_skip(self->buffer, 1);
size_t byte_index = self->buffer->bit_index >> 3;
int marker = self->buffer->bytes[byte_index];
if ((marker & 0xf0) == 0x20) {
if (!plm_buffer_has(self->buffer, 64)) {
return FALSE;
}
self->start_code = -1;
plm_buffer_skip(self->buffer, 4); // MPEG-1 marker
self->system_clock_ref = plm_demux_decode_time(self);
plm_buffer_skip(self->buffer, 1);
plm_buffer_skip(self->buffer, 22); // mux_rate * 50
plm_buffer_skip(self->buffer, 1);
}
else if ((marker & 0xc0) == 0x40) {
// MPEG-2 pack headers have a fixed 10-byte part followed by up to
// seven stuffing bytes. Read the stuffing length before consuming
// anything so that a partial input can be retried.
if (!plm_buffer_has(self->buffer, 80)) {
return FALSE;
}
byte_index = self->buffer->bit_index >> 3;
int stuffing_length = self->buffer->bytes[byte_index + 9] & 0x07;
if (!plm_buffer_has(self->buffer, (10 + stuffing_length) << 3)) {
return FALSE;
}
self->start_code = -1;
self->is_mpeg2 = TRUE;
plm_buffer_skip(self->buffer, 2); // MPEG-2 marker
self->system_clock_ref = plm_demux_decode_time(self);
plm_buffer_skip(self->buffer, 9); // SCR extension
plm_buffer_skip(self->buffer, 1);
plm_buffer_skip(self->buffer, 22); // program mux rate
plm_buffer_skip(self->buffer, 2); // marker bits
plm_buffer_skip(self->buffer, 5); // reserved
plm_buffer_skip(self->buffer, 3); // stuffing length
plm_buffer_skip(self->buffer, stuffing_length << 3);
}
else {
return FALSE;
}
self->has_pack_header = TRUE;
}
@@ -2085,6 +2122,65 @@ plm_packet_t *plm_demux_decode_packet(plm_demux_t *self, int type) {
self->next_packet.type = type;
self->next_packet.length = plm_buffer_read(self->buffer, 16);
self->next_packet.pts = PLM_PACKET_INVALID_TS;
if (self->is_mpeg2) {
// MPEG-2 PES packets have three fixed header bytes followed by a
// variable-length optional header. This decoder only needs PTS; all
// other optional fields can be skipped using the header length.
if (self->next_packet.length < 3 ||
plm_buffer_read(self->buffer, 2) != 0x02
) {
self->next_packet.length = 0;
return NULL;
}
plm_buffer_skip(self->buffer, 6); // scrambling and control flags
int pts_dts_marker = plm_buffer_read(self->buffer, 2);
plm_buffer_skip(self->buffer, 6); // remaining optional-field flags
int header_length = plm_buffer_read(self->buffer, 8);
if (
self->next_packet.length < (size_t)(3 + header_length) ||
!plm_buffer_has(self->buffer, header_length << 3)
) {
self->next_packet.length = 0;
return NULL;
}
int header_bytes_read = 0;
if (pts_dts_marker == 0x02) {
if (header_length < 5 || plm_buffer_read(self->buffer, 4) != 0x02) {
self->next_packet.length = 0;
return NULL;
}
self->next_packet.pts = plm_demux_decode_time(self);
header_bytes_read = 5;
}
else if (pts_dts_marker == 0x03) {
if (header_length < 10 || plm_buffer_read(self->buffer, 4) != 0x03) {
self->next_packet.length = 0;
return NULL;
}
self->next_packet.pts = plm_demux_decode_time(self);
if (plm_buffer_read(self->buffer, 4) != 0x01) {
self->next_packet.length = 0;
return NULL;
}
plm_demux_decode_time(self); // DTS
header_bytes_read = 10;
}
else if (pts_dts_marker != 0x00) {
self->next_packet.length = 0;
return NULL;
}
if (self->next_packet.pts != PLM_PACKET_INVALID_TS) {
self->last_decoded_pts = self->next_packet.pts;
}
plm_buffer_skip(self->buffer, (header_length - header_bytes_read) << 3);
self->next_packet.length -= 3 + header_length;
return plm_demux_get_packet(self);
}
self->next_packet.length -= plm_buffer_skip_bytes(self->buffer, 0xff); // stuffing
// skip P-STD
+83 -8
View File
@@ -19,6 +19,7 @@
#define RE_NR_BACK_CGS 16
#define RE_NR_INSTANCE_TARGETS 2
#define RE_NR_LIGHT_PARAMS 41 // .lit parameter count
#include <cglm/types.h>
@@ -27,10 +28,14 @@
typedef struct cJSON cJSON;
struct hash_table;
struct billboard_texture;
struct iarray_reader;
struct iarray_writer;
enum RE_plugin_version {
RE_REIGN_PLUGIN, // Toushin Toshi 3
RE_TAPIR_PLUGIN, // Rance Quest
RE_SEAL_PLUGIN, // Rance 9
};
enum RE_instance_type {
@@ -56,6 +61,29 @@ enum RE_fog_type {
RE_FOG_LIGHT_SCATTERING = 2,
};
// Indices into light_params. Vector entries name their first component.
enum seal_light_param {
SEAL_LP_TONEMAP_EXPOSURE_BIAS = 0,
SEAL_LP_TONEMAP_WHITE_POINT = 1,
SEAL_LP_TONEMAP_A = 2,
SEAL_LP_TONEMAP_B = 3,
SEAL_LP_TONEMAP_C = 4,
SEAL_LP_TONEMAP_D = 5,
SEAL_LP_TONEMAP_E = 6,
SEAL_LP_TONEMAP_F = 7,
SEAL_LP_HEMI_VEC = 8, // x,y,z
SEAL_LP_HEMI_SKY_COLOR = 11, // r,g,b
SEAL_LP_HEMI_MID_COLOR = 14, // r,g,b
SEAL_LP_HEMI_GROUND_COLOR = 17, // r,g,b
SEAL_LP_LS_BETA_R = 20,
SEAL_LP_LS_BETA_M = 21,
SEAL_LP_LS_G = 22,
SEAL_LP_LS_DISTANCE = 23,
SEAL_LP_LS_LIGHT_VEC = 24, // x,y,z
SEAL_LP_LS_LIGHT_COLOR = 27, // r,g,b
SEAL_LP_LS_SUN_COLOR = 30, // r,g,b
};
enum RE_draw_type {
RE_DRAW_TYPE_NORMAL = 0,
RE_DRAW_TYPE_ADDITIVE = 1,
@@ -64,15 +92,10 @@ enum RE_draw_type {
enum RE_draw_options {
RE_DRAW_OPTION_EDGE = 0,
RE_DRAW_OPTION_LIGHTING = 1,
RE_DRAW_OPTION_MAX
};
enum RE_draw_edge_mode {
RE_DRAW_EDGE_NONE = 0,
RE_DRAW_EDGE_CHARACTERS_ONLY = 1,
RE_DRAW_EDGE_ALL = 2,
};
struct RE_options {
int anti_aliasing;
int wait_vsync;
@@ -83,6 +106,12 @@ struct RE_camera {
vec3 pos;
float pitch, roll, yaw; // in degrees
float quake_pitch, quake_yaw;
// Per-frame full override from an 3de "camera" object. Cleared at the
// start of every RE_build_model tick and re-asserted by s3de_effect_update
// if any active camera object selects this frame.
bool override_active;
vec3 override_pos;
float override_pitch, override_yaw, override_roll;
};
struct RE_back_cg {
@@ -98,15 +127,17 @@ struct RE_back_cg {
struct RE_plugin {
struct draw_plugin plugin;
enum RE_plugin_version version;
int sprite;
int nr_instances;
struct RE_instance **instances;
struct archive *aar;
struct hash_table *model_cache;
struct hash_table *pae_cache;
struct hash_table *effect_cache;
struct RE_renderer *renderer;
struct RE_camera camera;
mat4 proj_transform;
int viewport_x, viewport_y, viewport_width, viewport_height;
struct RE_back_cg back_cg[RE_NR_BACK_CGS];
@@ -148,30 +179,43 @@ struct RE_plugin {
// TapirEngine
bool suspended;
// SealEngine
int mag_speed;
float light_params[RE_NR_LIGHT_PARAMS];
float edge_length;
float edge_reduction_rate;
vec3 edge_color;
};
struct RE_instance {
struct RE_plugin *plugin;
struct model *model;
struct particle_effect *effect;
struct s3de_effect *s3de_effect;
struct motion *motion;
struct motion *next_motion;
struct height_detector *height_detector;
enum RE_instance_type type;
struct string *name;
int target[RE_NR_INSTANCE_TARGETS];
vec3 pos;
float pitch, roll, yaw; // in degrees
vec3 scale;
float alpha;
float grayscale_rate;
bool draw;
bool draw_edge;
bool draw_shadow;
bool make_shadow;
float shadow_volume_bone_radius;
bool draw_bump;
float fps;
bool use_mag_speed;
bool motion_blend;
float motion_blend_rate;
vec3 diffuse; // color multiplier for meshes, light color for lights
vec3 ambient;
vec3 column_pos;
float column_height;
@@ -180,11 +224,15 @@ struct RE_instance {
// Billboards
enum RE_draw_type draw_type;
vec3 vertex_pos[4];
vec2 vertex_uv[4];
struct billboard_texture **billboard_frames;
int nr_billboard_frames;
// Lights
vec3 vec;
vec3 diffuse;
vec3 globe_diffuse;
float *light_params;
// Private
bool local_transform_needs_update;
@@ -198,6 +246,9 @@ struct RE_instance {
int texture_animation_index;
};
#define RE_MAX_PLUGINS 2
struct RE_plugin *RE_get_plugin(int handle);
struct RE_plugin *RE_plugin_new(enum RE_plugin_version version);
void RE_plugin_free(struct RE_plugin *plugin);
bool RE_plugin_bind(struct RE_plugin *plugin, int sprite);
@@ -205,18 +256,22 @@ bool RE_plugin_unbind(struct RE_plugin *plugin);
bool RE_plugin_suspend(struct RE_plugin *plugin);
bool RE_plugin_resume(struct RE_plugin *plugin);
bool RE_build_model(struct RE_plugin *plugin, int elapsed_ms);
void RE_update_model(struct RE_plugin *plugin);
bool RE_set_viewport(struct RE_plugin *plugin, int x, int y, int width, int height);
bool RE_set_projection(struct RE_plugin *plugin, float width, float height, float near, float far, float deg);
int RE_create_instance(struct RE_plugin *plugin);
bool RE_release_instance(struct RE_plugin *plugin, int instance);
bool RE_instance_set_type(struct RE_instance *instance, int type);
bool RE_instance_data_exists(struct RE_instance *instance, const char *name);
bool RE_instance_load(struct RE_instance *instance, const char *name);
bool RE_instance_motion_exists(struct RE_instance *instance, const char *name);
bool RE_instance_load_motion(struct RE_instance *instance, const char *name);
bool RE_instance_load_next_motion(struct RE_instance *instance, const char *name);
bool RE_instance_free_next_motion(struct RE_instance *instance);
bool RE_instance_set_mesh_show(struct RE_instance *instance, const char *mesh_name, bool show);
bool RE_instance_set_vertex_pos(struct RE_instance *instance, int index, float x, float y, float z);
bool RE_instance_set_vertex_uv(struct RE_instance *instance, int index, float u, float v);
int RE_instance_get_bone_index(struct RE_instance *instance, const char *name);
bool RE_instance_trans_local_pos_to_world_pos_by_bone(struct RE_instance *instance, int bone, vec3 offset, vec3 out);
float RE_instance_calc_height(struct RE_instance *instance, float x, float z);
@@ -227,6 +282,11 @@ bool RE_instance_find_path(struct RE_instance *instance, vec3 start, vec3 goal);
const vec3 *RE_instance_get_path_line(struct RE_instance *instance, int *nr_path_points);
bool RE_instance_optimize_path_line(struct RE_instance *instance);
bool RE_instance_calc_path_finder_intersect_eye_vec(struct RE_instance *instance, int mouse_x, int mouse_y, vec3 out);
bool RE_plugin_transform_pos_to_view_pos(struct RE_plugin *plugin, float x, float y, float z, int *view_x, int *view_y);
bool RE_plugin_get_camera_z_vector(struct RE_plugin *plugin, vec3 out);
void RE_plugin_reset_light_param(struct RE_plugin *plugin);
bool RE_plugin_set_fog_type(struct RE_plugin *plugin, int type);
int RE_plugin_get_fog_type(struct RE_plugin *plugin);
int RE_motion_get_state(struct motion *motion);
bool RE_motion_set_state(struct motion *motion, int state);
@@ -306,4 +366,19 @@ bool RE_back_cg_set_name(struct RE_back_cg *bcg, struct string *name, struct arc
void RE_render(struct sact_sprite *sp);
cJSON *RE_to_json(struct sact_sprite *sp, bool verbose);
// save.c
void RE_plugin_serialize(struct RE_plugin *plugin, struct iarray_writer *w);
void RE_plugin_deserialize(struct RE_plugin *plugin, struct iarray_reader *r, int version);
// Exposed for PartsEngine 3DLayer
int ReignEngine_create_plugin(enum RE_plugin_version version);
bool ReignEngine_ReleasePlugin(int handle);
bool ReignEngine_BindPlugin(int handle, int sprite);
// Exposed for MarmotModelEngine
void ReignEngine_update_models(void);
// debug.c
void re_debug_init(void);
#endif /* SYSTEM4_REIGN_H */
+2
View File
@@ -118,6 +118,8 @@ bool sact_DX_GetUsePower2Texture(void);
void sact_DX_SetUsePower2Texture(bool use);
extern struct text_style text_sprite_ts;
int StoatSpriteEngine_SP_SetDrawMethod(int sp_no, int method);
int StoatSpriteEngine_SP_GetDrawMethod(int sp_no);
bool StoatSpriteEngine_SP_SetTextSprite(int sp_no, struct string *text);
void StoatSpriteEngine_SP_SetTextSpriteType(int type);
void StoatSpriteEngine_SP_SetTextSpriteSize(int size);
+3 -2
View File
@@ -20,6 +20,7 @@ in vec2 tex_coord;
out vec4 frag_color;
void main() {
float gray = dot(texture(tex, tex_coord).rgb, vec3(0.299, 0.587, 0.114));
frag_color = vec4(vec3(gray), 1);
vec4 texel = texture(tex, tex_coord);
float gray = dot(texel.rgb, vec3(0.299, 0.587, 0.114));
frag_color = vec4(vec3(gray), texel.a);
}
+36
View File
@@ -0,0 +1,36 @@
/* Copyright (C) 2026 Nunuhara Cabbage <nunuhara@haniwa.technology>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://gnu.org/licenses/>.
*/
uniform sampler2D tex; // the new scene
uniform sampler2D old; // the old scene
uniform vec2 resolution; // the screen resolution
uniform float progress; // effect progress (0..1)
in vec2 tex_coord;
out vec4 frag_color;
#define EFFECT_HEIGHT 210.0
void main() {
// start position of effect
float top = ((resolution.y + EFFECT_HEIGHT) * (1.0 - progress)) - EFFECT_HEIGHT;
// current pixel y-coordinate
float p = tex_coord.y * resolution.y;
// calculate alpha value
float alpha = 1.0 - clamp((p - top) / EFFECT_HEIGHT, 0.0, 1.0);
vec3 col = mix(texture(tex, tex_coord).rgb, texture(old, tex_coord).rgb, alpha);
frag_color = vec4(col, 1.0);
}
+18 -35
View File
@@ -22,47 +22,30 @@ uniform float progress; // effect progress (0..1)
in vec2 tex_coord;
out vec4 frag_color;
#define MAX_STRIDE 128.0
#define MAX_CHUNK 128.0
vec3 get_pixel(vec2 xy) {
return mix(texture(old, xy).rgb, texture(tex, xy).rgb, progress);
}
void main() {
// size of each chunk
float stride = MAX_STRIDE * (1.0 - abs(progress * 2.0 - 1.0)); // progress=(0..0.5..1) -> stride=(0..max..0)
// offset of frag coordinate relative to chunk
vec2 offset = mod(gl_FragCoord.xy, stride);
// the chunk coordinate
vec2 chunk = tex_coord - offset / resolution;
// coordinate of chunk center
vec2 center = chunk + vec2(stride/2.0, stride/2.0) / resolution;
// FIXME: In this implementation, the anchor point is at the top left of the screen.
// It should be anchored at the center to match SACT2.dll.
frag_color = vec4(get_pixel(center), 1.0);
}
/* NOTE: This is an alternate implementation which computes an average for each chunk.
SACT2.dll does not do this.
#define SAMPLES 8.0
#define MAX_STRIDE 16.0
vec3 get_pixel(vec2 xy) {
return mix(texture(old, xy).xyz, texture(tex, xy).xyz, progress);
float triangle(float f) {
return 1.0 - abs(f * 2.0 - 1.0);
}
void main() {
float stride = MAX_STRIDE * (1.0 - abs(progress * 2.0 - 1.0)); // progress=(0..0.5..1) -> stride=(0..max..0)
vec3 avg = vec3(0, 0, 0);
vec2 xy = floor(gl_FragCoord.xy / (SAMPLES * stride)) * (SAMPLES * stride);
for (float j = 0.0; j < SAMPLES; j += 1.0) {
for (float i = 0.0; i < SAMPLES; i+= 1.0) {
avg += get_pixel((xy + stride * vec2(i, j)) / resolution);
}
}
avg /= SAMPLES * SAMPLES;
frag_color = vec4(avg, 1.0);
// center of the screen
vec2 center = resolution * 0.5;
// size of each chunk
float chunk_size = floor(triangle(progress) * MAX_CHUNK);
// number of chunks in each screen quadrant (including partial chunks at edges)
vec2 chunks_per_quadrant = floor(center / chunk_size);
// offset from screen to edge of grid (negative or zero)
vec2 grid_off = center - (chunks_per_quadrant * chunk_size);
// offset of pixel in chunk
vec2 chunk_off = mod(gl_FragCoord.xy - grid_off, chunk_size);
// the chunk coordinate
vec2 chunk = tex_coord - vec2(chunk_off) / resolution;
// coordinate of chunk center
vec2 chunk_center = chunk + vec2(chunk_size/2.0, chunk_size/2.0) / resolution;
frag_color = vec4(get_pixel(chunk_center), 1.0);
}
*/
+24 -6
View File
@@ -22,8 +22,21 @@ uniform float progress; // effect progress (0..1)
in vec2 tex_coord;
out vec4 frag_color;
vec3 get_pixel(vec2 xy) {
return mix(texture(old, xy).rgb, texture(tex, xy).rgb, progress);
#define M_PI 3.1415926535897932384626433832795
#define OLD_PERIOD ((2.0 * M_PI) / 360.0)
#define NEW_PERIOD ((2.0 * M_PI) / 720.0)
#define OLD_SHIFT (-M_PI)
#define NEW_SHIFT (M_PI / 2.0)
vec3 get_pixel(vec2 old_xy, vec2 new_xy) {
// influence of old color is described by: C_old = (1-t)^2
// influence of new color increases linearly: C_new = t
// the resulting blend is not normalized
vec3 old = texture(old, old_xy).rgb;
vec3 new = texture(tex, new_xy).rgb;
float old_weight = (1.0 - progress) * (1.0 - progress);
float new_weight = progress;
return (old * old_weight + new * new_weight);
}
float triangle(float f) {
@@ -31,9 +44,14 @@ float triangle(float f) {
}
void main() {
// old: x = amp * sin((2pi/360)*y + phase + -pi)
// new: x = amp * sin((2pi/720)*y + phase + pi/2)
vec2 p = tex_coord * resolution;
float scale = triangle(progress) * 80.0; // (0..0.5..1) -> (0..80..0);
float wobble = progress * 24.0;
float offset = sin((p.y / 100.0) - wobble) * scale;
frag_color = vec4(get_pixel(fract(vec2(p.x + offset, p.y) / resolution)), 1.0);
float amp = triangle(progress) * 80.0; // (0..0.5..1) -> (0..80..0);
float phase = progress * M_PI * 8.0;
float old_off = amp * sin(OLD_PERIOD * p.y + OLD_SHIFT + phase);
float new_off = amp * sin(NEW_PERIOD * p.y + NEW_SHIFT + phase);
vec2 old_xy = fract(vec2(p.x + old_off, p.y) / resolution);
vec2 new_xy = fract(vec2(p.x - new_off, p.y) / resolution);
frag_color = vec4(get_pixel(old_xy, new_xy), 1.0);
}
@@ -0,0 +1,58 @@
/* Copyright (C) 2019 Nunuhara Cabbage <nunuhara@haniwa.technology>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://gnu.org/licenses/>.
*/
uniform sampler2D tex; // the new scene
uniform sampler2D old; // the old scene
uniform vec2 resolution; // the screen resolution
uniform float progress; // effect progress (0..1)
in vec2 tex_coord;
out vec4 frag_color;
#define M_PI 3.1415926535897932384626433832795
#define OLD_PERIOD ((2.0 * M_PI) / 360.0)
#define NEW_PERIOD ((2.0 * M_PI) / 720.0)
#define OLD_SHIFT (-M_PI)
#define NEW_SHIFT (M_PI / 2.0)
vec3 get_pixel(vec2 old_xy, vec2 new_xy) {
// influence of old color is described by: C_old = (1-t)^2
// influence of new color increases linearly: C_new = t
// the resulting blend is not normalized
vec3 old = texture(old, old_xy).rgb;
vec3 new = texture(tex, new_xy).rgb;
float old_weight = (1.0 - progress) * (1.0 - progress);
float new_weight = progress;
return (old * old_weight + new * new_weight);
}
float triangle(float f) {
return 1.0 - abs(f * 2.0 - 1.0);
}
void main() {
// old: x = amp * sin((2pi/360)*y + phase + -pi)
// new: x = amp * sin((2pi/720)*y + phase + pi/2)
vec2 p = tex_coord * resolution;
float amp = triangle(progress) * 160.0; // (0..0.5..1) -> (0..160..0);
float phase = progress * M_PI * 8.0;
float old_off_x = amp * sin(OLD_PERIOD * p.y + OLD_SHIFT + phase);
float new_off_x = amp * sin(NEW_PERIOD * p.y + NEW_SHIFT + phase);
float off_y = resolution.y * progress;
vec2 old_xy = fract(vec2(p.x + old_off_x, p.y + off_y) / resolution);
vec2 new_xy = fract(vec2(p.x - new_off_x, p.y + off_y) / resolution);
frag_color = vec4(get_pixel(old_xy, new_xy), 1.0);
}
+32 -7
View File
@@ -20,21 +20,46 @@ uniform vec2 bot_left;
uniform vec2 top_right;
uniform vec3 add_color;
uniform vec3 multiply_color;
uniform int draw_filter;
uniform int use_clipper;
uniform sampler2D clipper_tex;
in vec2 tex_coord;
in vec2 clip_coord;
out vec4 frag_color;
float point_in_rect(vec2 p, vec2 bot_left, vec2 top_right) {
vec2 s = step(bot_left, p) - step(top_right, p);
return s.x * s.y;
const int DRAW_FILTER_MULTIPLY = 2;
const int DRAW_FILTER_SCREEN = 3;
bool inside_rect(vec2 p, vec2 bl, vec2 tr) {
return all(greaterThanEqual(p, bl)) && all(lessThan(p, tr));
}
void main() {
vec2 size = vec2(textureSize(tex, 0));
vec2 bl = bot_left / size;
vec2 tr = top_right / size;
vec2 size = vec2(textureSize(tex, 0));
if (!inside_rect(tex_coord, bot_left / size, top_right / size))
discard;
vec4 tex_color = texture(tex, tex_coord);
vec3 mod_color = (tex_color.rgb + add_color) * multiply_color;
frag_color = vec4(mod_color, tex_color.a * blend_rate) * point_in_rect(tex_coord, bl, tr);
float alpha = tex_color.a * blend_rate;
if (use_clipper != 0) {
if (!inside_rect(clip_coord, vec2(0.0), vec2(1.0)))
discard;
alpha *= texture(clipper_tex, clip_coord).a;
}
if (draw_filter == DRAW_FILTER_MULTIPLY) {
// result = mix(dst, mod_color*dst, alpha), used with (GL_DST_COLOR, GL_ZERO)
frag_color = vec4(mix(vec3(1.0), mod_color, alpha), 1.0);
} else if (draw_filter == DRAW_FILTER_SCREEN) {
// result = mod_color*alpha + (1 - mod_color*alpha)*dst,
// used with (GL_ONE, GL_ONE_MINUS_SRC_COLOR)
frag_color = vec4(mod_color * alpha, 1.0);
} else {
// normal or additive — blend func handles alpha
frag_color = vec4(mod_color, alpha);
}
}
+31
View File
@@ -0,0 +1,31 @@
/* Copyright (C) 2019 Nunuhara Cabbage <nunuhara@haniwa.technology>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://gnu.org/licenses/>.
*/
uniform mat4 world_transform;
uniform mat4 view_transform;
uniform mat4 inv_clipper_transform;
in vec4 vertex_pos;
in vec2 vertex_uv;
out vec2 tex_coord;
out vec2 clip_coord;
void main() {
vec4 world_pos = world_transform * vertex_pos;
gl_Position = view_transform * world_pos;
tex_coord = vertex_uv;
clip_coord = (inv_clipper_transform * world_pos).xy;
}
+95 -24
View File
@@ -24,11 +24,15 @@
#define ALPHA_MAP_BLEND 2
#define ALPHA_MAP_TEST 3
#define FOG_LINEAR 1
#define FOG_LIGHT_SCATTERING 2
#define ENABLE_SPECULAR (ENGINE == REIGN_ENGINE || ENGINE == SEAL_ENGINE)
#define ENABLE_LIGHT_SCATTERING (ENGINE == REIGN_ENGINE || ENGINE == SEAL_ENGINE)
#if ENGINE == REIGN_ENGINE
#define NR_DIR_LIGHTS 3
#define FOG_LINEAR 1
#define FOG_LIGHT_SCATTERING 2
struct dir_light {
vec3 dir;
@@ -36,25 +40,17 @@ struct dir_light {
vec3 globe_diffuse;
};
uniform sampler2D specular_texture;
uniform dir_light dir_lights[NR_DIR_LIGHTS];
uniform float specular_strength;
uniform float specular_shininess;
uniform bool use_specular_map;
uniform float rim_exponent;
uniform vec3 rim_color;
uniform int fog_type;
uniform float fog_near;
uniform float fog_far;
uniform vec3 fog_color;
in vec3 light_dir[NR_DIR_LIGHTS];
in vec3 specular_dir;
in vec3 ls_ex;
in vec3 ls_in;
uniform vec3 global_ambient;
vec3 dir_lights_diffuse(vec3 norm)
vec3 compute_diffuse(vec3 norm)
{
vec3 diffuse = vec3(0.0);
for (int i = 0; i < NR_DIR_LIGHTS; i++) {
@@ -64,18 +60,75 @@ vec3 dir_lights_diffuse(vec3 norm)
return diffuse;
}
#else // ENGINE == REIGN_ENGINE
#elif ENGINE == SEAL_ENGINE
const vec3 global_ambient = vec3(0.0);
vec3 dir_lights_diffuse(vec3 norm)
// Hemisphere lighting (sky/middle/ground).
uniform vec3 hemi_light_dir;
uniform vec3 hemi_sky_color;
uniform vec3 hemi_mid_color;
uniform vec3 hemi_ground_color;
vec3 compute_diffuse(vec3 norm)
{
float t = 0.5 * dot(norm, hemi_light_dir) + 0.5;
vec3 low_mid = mix(hemi_ground_color, hemi_mid_color, t);
vec3 mid_high = mix(hemi_mid_color, hemi_sky_color, t);
return mix(low_mid, mid_high, t);
}
#else // TAPIR_ENGINE
const vec3 global_ambient = vec3(0.0);
vec3 compute_diffuse(vec3 norm)
{
return vec3(1.0);
}
#endif // ENGINE == REIGN_ENGINE
#if ENABLE_LIGHT_SCATTERING
uniform int fog_type;
in vec3 ls_ex;
in vec3 ls_in;
#endif
#if ENABLE_SPECULAR
uniform sampler2D specular_texture;
uniform vec3 specular_color;
uniform float specular_shininess;
uniform bool use_specular_map;
in vec3 specular_dir;
#endif
#if ENGINE == SEAL_ENGINE
// Uncharted 2 (Hable) filmic tone mapping.
uniform vec4 tonemap_param; // (ExposureBias, WhitePoint, A, B)
uniform vec4 tonemap_param2; // (C, D, E, F)
uniform bool nolighting;
uniform float grayscale_rate;
vec3 hable(vec3 v)
{
float a = tonemap_param.z, b = tonemap_param.w;
float c = tonemap_param2.x, d = tonemap_param2.y;
float e = tonemap_param2.z, f = tonemap_param2.w;
return ((v * (a * v + c * b) + d * e) / (v * (a * v + b) + d * f)) - e / f;
}
vec3 tone_map(vec3 color)
{
vec3 curr = hable(color * tonemap_param.x);
vec3 white = hable(vec3(tonemap_param.y));
return curr / white;
}
#endif
uniform sampler2D tex;
uniform sampler2D blend_tex;
uniform bool use_blend_texture;
uniform sampler2D alpha_texture;
uniform sampler2D light_texture;
uniform sampler2D shadow_texture;
@@ -85,6 +138,7 @@ uniform bool use_normal_map;
uniform sampler2D normal_texture;
uniform vec3 instance_ambient;
uniform vec3 diffuse_mod;
uniform int diffuse_type;
uniform float shadow_darkness;
uniform float shadow_bias;
@@ -98,6 +152,8 @@ in vec4 shadow_frag_pos;
in float dist;
in vec3 eye;
in vec3 normal;
in float blend_weight;
in vec2 blend_tex_coord;
out vec4 frag_color;
void main() {
@@ -114,33 +170,36 @@ void main() {
// Diffuse lighting
vec4 texel;
vec3 diffuse = vec3(1.0);
if (diffuse_type == DIFFUSE_ENV_MAP) {
texel = texture(tex, (vec2(normal.x, -normal.y) + 1.0) / 2.0);
frag_rgb = texel.rgb;
} else {
texel = texture(tex, tex_coord);
if (diffuse_type == DIFFUSE_EMISSIVE) {
frag_rgb = texel.rgb;
} else {
vec3 diffuse = dir_lights_diffuse(norm);
if (use_blend_texture) {
texel = mix(texel, texture(blend_tex, blend_tex_coord), blend_weight);
}
if (diffuse_type != DIFFUSE_EMISSIVE) {
diffuse = compute_diffuse(norm);
if (diffuse_type == DIFFUSE_LIGHT_MAP) {
diffuse *= texture(light_texture, light_tex_coord).rgb;
}
frag_rgb += texel.rgb * diffuse * color_mod.rgb;
}
}
frag_rgb += texel.rgb * diffuse * color_mod.rgb * diffuse_mod;
#if ENGINE == REIGN_ENGINE
#if ENABLE_SPECULAR
// Specular lighting
if (specular_strength > 0.0) {
if (any(greaterThan(specular_color, vec3(0.0)))) {
vec3 reflect_dir = reflect(specular_dir, norm);
float specular = pow(max(dot(view_dir, reflect_dir), 0.0), specular_shininess) * specular_strength;
float specular = pow(max(dot(view_dir, reflect_dir), 0.0), specular_shininess);
if (use_specular_map) {
specular *= texture(specular_texture, tex_coord).r;
}
frag_rgb += vec3(specular);
frag_rgb += specular_color * specular;
}
#endif
#if ENGINE == REIGN_ENGINE
// Rim lighting
if (rim_exponent > 0.0) {
// Normal map is not used here.
@@ -152,7 +211,11 @@ void main() {
if (fog_type == FOG_LINEAR) {
float fog_factor = clamp((dist - fog_near) / (fog_far - fog_near), 0.0, 1.0);
frag_rgb = mix(frag_rgb, fog_color, fog_factor);
} else if (fog_type == FOG_LIGHT_SCATTERING) {
}
#endif
#if ENABLE_LIGHT_SCATTERING
if (fog_type == FOG_LIGHT_SCATTERING) {
frag_rgb = frag_rgb * ls_ex + ls_in;
}
#endif
@@ -174,6 +237,14 @@ void main() {
frag_rgb *= brightness;
}
#if ENGINE == SEAL_ENGINE
if (!nolighting)
frag_rgb = tone_map(frag_rgb);
if (grayscale_rate != 0.0)
frag_rgb = mix(frag_rgb, vec3(dot(frag_rgb, vec3(0.299, 0.587, 0.114))), grayscale_rate);
#endif
// Alpha mapping
float alpha = texel.a * color_mod.a * alpha_mod;
if (alpha_mode == ALPHA_TEST) {
+34 -13
View File
@@ -16,10 +16,14 @@
const float PI = 3.14159265358979323846;
#define FOG_LIGHT_SCATTERING 2
#define ENABLE_SPECULAR (ENGINE == REIGN_ENGINE || ENGINE == SEAL_ENGINE)
#define ENABLE_LIGHT_SCATTERING (ENGINE == REIGN_ENGINE || ENGINE == SEAL_ENGINE)
#if ENGINE == REIGN_ENGINE
#define NR_DIR_LIGHTS 3
#define FOG_LIGHT_SCATTERING 2
struct dir_light {
vec3 dir;
@@ -28,16 +32,7 @@ struct dir_light {
};
uniform dir_light dir_lights[NR_DIR_LIGHTS];
uniform vec3 specular_light_dir;
uniform int fog_type;
uniform vec4 ls_params; // (beta_r, beta_m, g, distance)
uniform vec3 ls_light_dir;
uniform vec3 ls_light_color;
uniform vec3 ls_sun_color;
out vec3 light_dir[NR_DIR_LIGHTS];
out vec3 specular_dir;
out vec3 ls_ex;
out vec3 ls_in;
const vec2 uv_scroll = vec2(0.0);
@@ -47,6 +42,23 @@ uniform vec2 uv_scroll;
#endif // ENGINE == REIGN_ENGINE
uniform vec4 uv_tiling;
#if ENABLE_LIGHT_SCATTERING
uniform int fog_type;
uniform vec4 ls_params; // (beta_r, beta_m, g, distance)
uniform vec3 ls_light_dir;
uniform vec3 ls_light_color;
uniform vec3 ls_sun_color;
out vec3 ls_ex;
out vec3 ls_in;
#endif
#if ENABLE_SPECULAR
uniform vec3 specular_light_dir;
out vec3 specular_dir;
#endif
uniform mat4 local_transform;
uniform mat4 view_transform;
uniform mat4 proj_transform;
@@ -72,6 +84,8 @@ in vec4 vertex_color;
in vec4 vertex_tangent;
in ivec4 vertex_bone_index;
in vec4 vertex_bone_weight;
in float vertex_blend_weight;
in vec2 vertex_blend_uv;
out vec2 tex_coord;
out vec2 light_tex_coord;
@@ -81,6 +95,8 @@ out vec4 shadow_frag_pos;
out float dist;
out vec3 eye;
out vec3 normal;
out float blend_weight;
out vec2 blend_tex_coord;
void main() {
mat4 local_bone_transform = local_transform;
@@ -114,9 +130,11 @@ void main() {
vec4 view_pos = view_transform * world_pos;
gl_Position = proj_transform * view_pos;
tex_coord = vertex_uv + uv_scroll;
tex_coord = (vertex_uv + uv_scroll) * uv_tiling.xy;
light_tex_coord = vertex_light_uv + uv_scroll;
color_mod = vertex_color;
blend_weight = vertex_blend_weight;
blend_tex_coord = (vertex_blend_uv + uv_scroll) * uv_tiling.zw;
dist = -view_pos.z;
shadow_frag_pos = shadow_transform * world_pos;
@@ -125,12 +143,15 @@ void main() {
frag_pos = TBN * vec3(world_pos);
eye = TBN * camera_pos;
#if ENABLE_SPECULAR
specular_dir = TBN * specular_light_dir;
#endif
#if ENGINE == REIGN_ENGINE
light_dir[0] = TBN * dir_lights[0].dir;
light_dir[1] = TBN * dir_lights[1].dir;
light_dir[2] = TBN * dir_lights[2].dir;
specular_dir = TBN * specular_light_dir;
#endif
#if ENABLE_LIGHT_SCATTERING
if (fog_type == FOG_LIGHT_SCATTERING) {
float beta_r = ls_params.x;
float beta_m = ls_params.y;
+304 -9
View File
@@ -40,9 +40,9 @@ struct model {
int nr_materials;
struct material *materials;
int nr_bones;
struct bone *bones;
struct hash_table *bone_map; // bone id in POL/MOT -> struct bone *
struct hash_table *bone_name_map; // bone name -> (struct bone * | NULL)
struct bone *bones; // ordered so that parents precede their children
struct bone **bones_by_pol_index; // POL bone array index -> struct bone *
struct hash_table *bone_map; // bone id in POL -> struct bone *
struct hash_table *mot_cache; // name -> struct mot *
struct collider *collider;
vec3 aabb[2]; // axis-aligned bounding box
@@ -63,17 +63,22 @@ struct mesh {
vec3 outline_color;
float outline_thickness;
vec2 uv_scroll;
vec3 specular_color;
float specular_power;
};
struct material {
uint32_t flags;
bool is_transparent;
vec2 uv_tiling;
vec2 blend_uv_tiling;
int nr_color_maps;
GLuint *color_maps;
GLuint specular_map;
GLuint alpha_map;
GLuint light_map;
GLuint normal_map;
GLuint blend_texture;
float specular_strength;
float specular_shininess;
float shadow_darkness;
@@ -108,6 +113,8 @@ enum RE_attribute_location {
VATTR_LIGHT_UV,
VATTR_COLOR,
VATTR_TANGENT,
VATTR_BLEND_WEIGHT,
VATTR_BLEND_UV,
};
struct shadow_renderer {
@@ -152,6 +159,7 @@ struct RE_renderer {
GLint has_bones;
GLint global_ambient;
GLint instance_ambient;
GLint diffuse_mod;
struct {
GLint dir;
GLint diffuse;
@@ -159,7 +167,7 @@ struct RE_renderer {
GLint ambient;
} dir_lights[NR_DIR_LIGHTS];
GLint specular_light_dir;
GLint specular_strength;
GLint specular_color;
GLint specular_shininess;
GLint use_specular_map;
GLint specular_texture;
@@ -182,13 +190,27 @@ struct RE_renderer {
GLint ls_light_dir;
GLint ls_light_color;
GLint ls_sun_color;
GLint hemi_light_dir;
GLint hemi_sky_color;
GLint hemi_mid_color;
GLint hemi_ground_color;
GLint tonemap_param;
GLint tonemap_param2;
GLint nolighting;
GLint grayscale_rate;
GLint alpha_mode;
GLint alpha_texture;
GLint uv_scroll;
GLint uv_tiling;
GLint blend_tex;
GLint use_blend_texture;
GLuint billboard_vao;
GLuint billboard_attr_buffer;
struct hash_table *billboard_textures; // cg_no -> struct billboard_texture*
GLuint billboard_particle_vao;
GLuint billboard_particle_attr_buffer;
GLuint billboard_instance_vao;
GLuint billboard_instance_attr_buffer;
struct hash_table *billboard_textures_by_no; // cg_no -> struct billboard_texture*
struct hash_table *billboard_textures_by_path; // path -> struct billboard_texture*
uint32_t last_frame_timestamp;
};
@@ -202,6 +224,7 @@ struct billboard_texture {
extern enum RE_plugin_version re_plugin_version;
struct RE_instance *RE_create_instance_at(struct RE_plugin *plugin, int index);
void RE_instance_update_local_transform(struct RE_instance *inst);
// model.c
@@ -220,7 +243,8 @@ struct archive_data *RE_get_aar_entry(struct archive *aar, const char *dir, cons
struct RE_renderer *RE_renderer_new(void);
void RE_renderer_free(struct RE_renderer *r);
void RE_renderer_set_viewport_size(struct RE_renderer *r, int width, int height);
bool RE_renderer_load_billboard_texture(struct RE_renderer *r, int cg_no);
bool RE_renderer_load_billboard_texture_by_no(struct RE_renderer *r, int cg_no);
struct billboard_texture *RE_renderer_load_billboard_texture_by_path(struct RE_renderer *r, struct archive *aar, const char *path);
struct height_detector *RE_renderer_create_height_detector(struct RE_renderer *r, struct model *model);
void RE_renderer_free_height_detector(struct height_detector *hd);
bool RE_renderer_detect_height(struct height_detector *hd, float x, float z, float *y_out);
@@ -396,6 +420,218 @@ void particle_effect_free(struct particle_effect *effect);
void particle_effect_update(struct RE_instance *inst);
void particle_object_calc_local_transform(struct RE_instance *inst, struct particle_object *po, struct particle_instance *pi, float frame, mat4 dest);
// s3de.c
// .3de effect system. The 's' prefix is just to avoid an identifier starting
// with a digit; it stands for SealEngine.
enum s3de_object_type {
S3DE_OBJ_BILLBOARD,
S3DE_OBJ_POLYGON,
S3DE_OBJ_CAMERA,
};
enum s3de_move_type {
S3DE_MOVE_LINEAR,
S3DE_MOVE_BEZIER,
};
enum s3de_spawn_position_type {
S3DE_SPAWN_NORMAL,
S3DE_SPAWN_SPHERE,
S3DE_SPAWN_HORIZONTAL,
};
enum s3de_blend_type {
S3DE_BLEND_NORMAL,
S3DE_BLEND_ADDITIVE,
};
enum s3de_emitter_link_type {
S3DE_LINK_NONE = 0, // frozen at spawn-time world pos
S3DE_LINK_FOLLOW = 1, // particle follows current emitter pos
S3DE_LINK_ROTATE_AT_SPAWN = 2, // emitter rotation around spawn pos
S3DE_LINK_ROTATE_AT_CURRENT = 3, // emitter rotation around current pos
S3DE_LINK_MAX = S3DE_LINK_ROTATE_AT_CURRENT
};
enum s3de_direction_type {
S3DE_DIR_RANDOM,
S3DE_DIR_SPECIFIED,
S3DE_DIR_EMITTER,
S3DE_DIR_EMITTER_REVERSE,
S3DE_DIR_EMITTER_COORD,
S3DE_DIR_ARBITRARY_PLANE,
S3DE_DIR_SPAWN,
};
enum s3de_posture_type {
S3DE_POSE_NONE,
S3DE_POSE_CAMERA,
S3DE_POSE_MOVE_DIR,
S3DE_POSE_MOVE_AND_FLY,
S3DE_POSE_FLY_AND_EMITTER_MOVE,
};
enum s3de_interp_type {
S3DE_INTERP_NONE,
S3DE_INTERP_LINEAR,
S3DE_INTERP_SLERP,
};
struct s3de_range {
float base;
float random;
};
struct s3de_scalar_entry {
int frame;
float value;
enum s3de_interp_type interp;
};
struct s3de_vec3_entry {
int frame;
vec3 v;
enum s3de_interp_type interp;
};
struct s3de_position_entry {
int frame;
vec3 pos;
enum s3de_interp_type interp;
float spawn_radius;
float spawn_angle_deg;
};
struct s3de_object {
char *name;
enum s3de_object_type type;
enum s3de_move_type movement_type;
enum s3de_spawn_position_type spawn_position_type;
float spawn_distance;
int particle_count;
char *texture;
enum s3de_blend_type blend_type;
char *polygon_name;
enum s3de_emitter_link_type emitter_link_type;
bool soft_fog_edge;
enum s3de_posture_type posture_type;
enum s3de_direction_type direction_type;
vec3 direction;
float direction_angle;
struct s3de_range start_size, end_size;
struct s3de_range start_x_size, end_x_size;
struct s3de_range start_y_size, end_y_size;
struct s3de_range offset_x, offset_y, offset_z;
struct s3de_range speed, acceleration;
struct s3de_range movement_distance, movement_curve;
bool free_fall;
float mass, air_resistance;
struct s3de_range start_x_rotation, end_x_rotation;
struct s3de_range start_y_rotation, end_y_rotation;
struct s3de_range start_z_rotation, end_z_rotation;
float x_revolution_angle[2], y_revolution_angle[2], z_revolution_angle[2];
float x_revolution_distance[2], y_revolution_distance[2], z_revolution_distance[2];
int child_frame;
float texture_anime_frame;
struct s3de_range alpha_fade_in_frame, alpha_fade_out_frame;
int *damages;
int nr_damages;
struct s3de_position_entry *position_list;
int nr_position_entries;
struct s3de_scalar_entry *size_list;
int nr_size_entries;
struct s3de_scalar_entry *x_size_list;
int nr_x_size_entries;
struct s3de_scalar_entry *y_size_list;
int nr_y_size_entries;
struct s3de_scalar_entry *alpha_list;
int nr_alpha_entries;
struct s3de_vec3_entry *rotation_list;
int nr_rotation_entries;
struct s3de_vec3_entry *multiply_color_list;
int nr_multiply_color_entries;
struct s3de_vec3_entry *additive_color_list;
int nr_additive_color_entries;
struct model *model;
};
struct s3de {
char *path;
int loop_start_frame;
int loop_end_frame;
struct s3de_object *objects;
int nr_objects;
struct hash_table *textures; // name -> struct billboard_texture*
};
struct s3de *s3de_load(struct archive *aar, const char *path);
void s3de_free(struct s3de *s);
struct s3de_particle {
float begin_frame, end_frame;
vec3 world_pos; // spawn-time world position
vec3 spawn_offset; // offset from emitter pos
vec3 direction;
vec3 spawn_dir; // emitter motion direction at spawn frame
float speed, accel;
float movement_distance, movement_curve;
float start_size_scale, end_size_scale;
float start_x_scale, end_x_scale;
float start_y_scale, end_y_scale;
float offset_x, offset_y; // per-particle 2D quad pivot shift
vec3 start_rotation_deg, end_rotation_deg;
float fade_in_frames, fade_out_frames;
};
// Emitter-level values interpolated once per frame by s3de_effect_update,
// then read (and combined with per-particle values) during rendering.
struct s3de_object_state {
vec3 emitter_pos;
float emitter_size, emitter_x_size, emitter_y_size;
float emitter_alpha;
vec3 emitter_rotation_deg;
vec3 multiply_color;
vec3 additive_color;
struct s3de_particle *particles; // length = obj->particle_count
};
struct s3de_effect {
struct s3de *s3de;
struct s3de_object_state *objects; // length = s3de->nr_objects
int wav_channel; // -1 if no sound
bool sound_started;
float last_frame;
};
struct s3de_effect *s3de_effect_create(struct s3de *s, struct archive *aar);
void s3de_effect_free(struct s3de_effect *eff);
void s3de_effect_update(struct RE_instance *inst);
void s3de_calc_frame_range(struct s3de *s, struct motion *motion);
bool s3de_particle_alpha(struct s3de_object_state *st, struct s3de_particle *p,
float frame, float *alpha_out);
bool s3de_billboard_world_transform(struct RE_instance *inst,
struct s3de_object *obj, struct s3de_object_state *st,
struct s3de_particle *p, float frame, mat3 camera_rot,
mat4 out);
bool s3de_mesh_world_transform(struct RE_instance *inst,
struct s3de_object *obj, struct s3de_object_state *st,
struct s3de_particle *p, float frame, mat3 camera_rot, vec3 camera_pos,
mat4 out);
// parser.c
struct pol {
@@ -426,12 +662,13 @@ struct pol_material {
char *name;
uint32_t flags;
char *textures[MAX_TEXTURE_TYPE];
vec2 uv_tiling;
};
struct pol_material_group {
struct pol_material m;
uint32_t nr_children;
struct pol_material *children;
struct pol_material_group *children;
};
enum mesh_flags {
@@ -445,6 +682,11 @@ enum mesh_flags {
MESH_NO_HEIGHT_DETECTION = 1 << 7,
MESH_ALPHA = 1 << 8,
MESH_HAS_LIGHT_UV = 1 << 9,
MESH_NO_ZWRITE = 1 << 10,
MESH_HEIGHT_DETECTION = 1 << 11,
MESH_HAS_SPECULAR_COLOR = 1 << 12,
MESH_HAS_SPECULAR_POWER = 1 << 13,
MESH_NO_DRAWSHADOW = 1 << 14,
};
struct pol_mesh {
@@ -461,12 +703,18 @@ struct pol_mesh {
vec3 *colors;
uint32_t nr_alphas;
float *alphas;
uint32_t nr_blend_uvs;
vec2 *blend_uvs;
uint32_t nr_blend_weights;
float *blend_weights;
uint32_t nr_triangles;
struct pol_triangle *triangles;
// Parameters in .opr file.
Color edge_color;
float edge_size;
vec2 uv_scroll;
vec3 specular_color;
float specular_power;
};
struct pol_vertex {
@@ -486,6 +734,8 @@ struct pol_triangle {
uint32_t light_uv_index[3];
uint32_t color_index[3];
uint32_t alpha_index[3];
uint32_t blend_uv_index[3];
uint32_t blend_weight_index[3];
vec3 normals[3];
uint32_t material_group_index;
};
@@ -504,6 +754,7 @@ struct mot {
uint32_t nr_bones;
uint32_t nr_texture_indices;
uint32_t *texture_indices; // texture index for each frame
struct mpr *mpr; // optional
struct mot_bone *motions[];
};
@@ -557,6 +808,49 @@ struct amt_material *amt_find_material(struct amt *amt, const char *name);
void opr_load(uint8_t *data, size_t size, struct pol *pol);
void txa_load(uint8_t *data, size_t size, struct mot *mot);
void lit_reset(float *out);
bool lit_parse(uint8_t *data, size_t size, float *out);
// mpr.c
struct mpr_float_key { int frame; float v; };
struct mpr_vec3_key { int frame; vec3 v; };
struct mpr_int_key { int frame; int v; };
struct mpr_track_set {
int nr_mul_alpha;
struct mpr_float_key *mul_alpha;
int nr_mul_diffuse;
struct mpr_vec3_key *mul_diffuse;
int nr_add_ambient;
struct mpr_vec3_key *add_ambient;
int nr_texture_anime;
struct mpr_int_key *texture_anime; // mesh-scope only
};
struct mpr {
int nr_meshes; // == model->nr_meshes
struct mpr_track_set **mesh_tracks; // sparse: indexed by mesh index, NULL if absent
struct mpr_track_set object;
bool has_mesh_alpha; // any mesh_tracks[i]->nr_mul_alpha > 0
};
// Per-draw material modulation.
struct mpr_modulation {
float alpha;
vec3 ambient;
vec3 diffuse;
};
struct mpr *mpr_load(uint8_t *data, size_t size, struct model *model);
void mpr_free(struct mpr *mpr);
void mpr_evaluate_object(const struct mpr *mpr, float frame,
struct RE_instance *inst, struct mpr_modulation *out);
void mpr_evaluate_mesh(const struct mpr_track_set *mt, float frame,
const struct mpr_modulation *obj, struct mpr_modulation *out);
void mpr_build_mat_tex_index(const struct model *model, const struct RE_instance *inst,
const struct mpr *mpr, float frame, int *mat_tex_index);
// collision.c
struct collider_triangle;
@@ -572,6 +866,7 @@ struct collider {
};
struct collider *collider_create(struct pol_mesh *mesh);
struct collider *collider_create_raycast(struct pol_mesh **meshes, int nr_meshes);
void collider_free(struct collider *collider);
bool collider_height(struct collider *collider, vec2 xz, float *h_out);
bool check_collision(struct collider *collider, vec2 p0, vec2 p1, float radius, vec2 out);
+33 -8
View File
@@ -15,6 +15,7 @@
*/
#include <assert.h>
#include <float.h>
#include <stdlib.h>
#include <cglm/cglm.h>
@@ -36,11 +37,8 @@ struct collider_edge {
vec2 aabb[2];
};
static void init_triangles(struct collider *collider, struct pol_mesh *mesh)
static struct collider_triangle *fill_triangles(struct collider_triangle *t, struct pol_mesh *mesh)
{
collider->nr_triangles = mesh->nr_triangles;
collider->triangles = xcalloc(mesh->nr_triangles, sizeof(struct collider_triangle));
struct collider_triangle *t = collider->triangles;
for (int tri_i = 0; tri_i < mesh->nr_triangles; tri_i++, t++) {
glm_aabb2d_invalidate(t->aabb);
for (int i = 0; i < 3; i++) {
@@ -65,6 +63,14 @@ static void init_triangles(struct collider *collider, struct pol_mesh *mesh)
t->neighbors[i] = -1;
}
}
return t;
}
static void init_triangles(struct collider *collider, struct pol_mesh *mesh)
{
collider->nr_triangles = mesh->nr_triangles;
collider->triangles = xcalloc(mesh->nr_triangles, sizeof(struct collider_triangle));
fill_triangles(collider->triangles, mesh);
}
static void link_neighbors(struct collider *collider, int t1_index, int t2_index)
@@ -137,6 +143,20 @@ struct collider *collider_create(struct pol_mesh *mesh)
return collider;
}
// Creates a collider for raycasting only (no edges / neighbor links), from the
// union of the given meshes' triangles. Used for SealEngine HeightDetection meshes.
struct collider *collider_create_raycast(struct pol_mesh **meshes, int nr_meshes)
{
struct collider *collider = xcalloc(1, sizeof(struct collider));
for (int i = 0; i < nr_meshes; i++)
collider->nr_triangles += meshes[i]->nr_triangles;
collider->triangles = xcalloc(collider->nr_triangles, sizeof(struct collider_triangle));
struct collider_triangle *t = collider->triangles;
for (int i = 0; i < nr_meshes; i++)
t = fill_triangles(t, meshes[i]);
return collider;
}
void collider_free(struct collider *collider)
{
free(collider->triangles);
@@ -275,16 +295,21 @@ bool check_collision(struct collider *collider, vec2 p0, vec2 p1, float radius,
bool collider_raycast(struct collider *collider, vec3 origin, vec3 direction, vec3 out)
{
// Return the intersection nearest to the ray origin (closest to the camera).
float nearest = FLT_MAX;
struct collider_triangle* t = collider->triangles;
for (; t < collider->triangles + collider->nr_triangles; t++) {
float d;
if (glm_ray_triangle(origin, direction, t->vertices[0], t->vertices[1], t->vertices[2], &d)) {
glm_vec3_scale(direction, d, out);
glm_vec3_add(origin, out, out);
return true;
if (d < nearest)
nearest = d;
}
}
return false;
if (nearest == FLT_MAX)
return false;
glm_vec3_scale(direction, nearest, out);
glm_vec3_add(origin, out, out);
return true;
}
struct pathfinder_node {
+249 -2
View File
@@ -18,8 +18,12 @@
#include "3d_internal.h"
#include "cJSON.h"
#include "debugger.h"
#include "gfx/gfx.h"
#include "json.h"
#include "reign.h"
#include "sact.h"
#include "scene.h"
#include "sprite.h"
#include "xsystem4.h"
@@ -115,6 +119,9 @@ static cJSON *instance_to_json(struct RE_instance *inst, int index, bool verbose
if (inst->effect) {
cJSON_AddStringToObject(obj, "path", inst->effect->pae->path);
}
if (inst->s3de_effect && inst->s3de_effect->s3de) {
cJSON_AddStringToObject(obj, "path", inst->s3de_effect->s3de->path);
}
return obj;
}
@@ -143,9 +150,8 @@ static cJSON *back_cg_to_json(struct RE_back_cg *bcg, int index, bool verbose)
return obj;
}
cJSON *RE_to_json(struct sact_sprite *sp, bool verbose)
static cJSON *re_plugin_to_json(struct RE_plugin *p, bool verbose)
{
struct RE_plugin *p = (struct RE_plugin*)sp->plugin;
cJSON *obj, *cam, *a;
obj = cJSON_CreateObject();
@@ -198,3 +204,244 @@ cJSON *RE_to_json(struct sact_sprite *sp, bool verbose)
return obj;
}
cJSON *RE_to_json(struct sact_sprite *sp, bool verbose)
{
return re_plugin_to_json((struct RE_plugin*)sp->plugin, verbose);
}
#ifdef DEBUGGER_ENABLED
static int dbg_current_plugin = -1;
// Returns the plugin number to use. Falls back to the only existing plugin
// if no current is set. Prints an error and returns -1 if neither holds.
static int dbg_resolve_plugin(void)
{
if (dbg_current_plugin >= 0) {
if (RE_get_plugin(dbg_current_plugin))
return dbg_current_plugin;
DBG_ERROR("Current plugin %d no longer exists", dbg_current_plugin);
dbg_current_plugin = -1;
return -1;
}
int found = -1;
for (int h = 0; h < RE_MAX_PLUGINS; h++) {
if (!RE_get_plugin(h))
continue;
if (found >= 0) {
DBG_ERROR("Multiple plugins exist; use '3d plugin <no>' to choose one");
return -1;
}
found = h;
}
if (found < 0) {
DBG_ERROR("No ReignEngine plugins");
return -1;
}
return found;
}
static struct RE_plugin *dbg_current_re_plugin(void)
{
int no = dbg_resolve_plugin();
return no < 0 ? NULL : RE_get_plugin(no);
}
static bool instance_has_content(struct RE_instance *inst)
{
if (inst->type == RE_ITYPE_UNINITIALIZED)
return false;
if (inst->model && inst->model->path)
return true;
if (inst->motion && inst->motion->mot)
return true;
if (inst->effect && inst->effect->pae)
return true;
if (inst->s3de_effect && inst->s3de_effect->s3de)
return true;
return false;
}
static void instance_list_print(struct RE_instance *inst, int index, int indent)
{
indent_message(indent, inst->draw ? "+ " : "- ");
sys_message("instance %d (%s)", index, instance_type_name(inst->type));
if (inst->model && inst->model->path)
sys_message(" model=%s", display_sjis0(inst->model->path));
if (inst->motion && inst->motion->mot)
sys_message(" motion=%s/%s frame=%.2f",
display_sjis0(inst->motion->mot->name),
motion_state_name(inst->motion->state),
inst->motion->current_frame);
if (inst->effect && inst->effect->pae)
sys_message(" effect=%s", display_sjis0(inst->effect->pae->path));
if (inst->s3de_effect && inst->s3de_effect->s3de)
sys_message(" s3de=%s", display_sjis0(inst->s3de_effect->s3de->path));
sys_message("\n");
}
static void re_cmd_list(unsigned nr_args, char **args)
{
bool show_all = nr_args >= 1 && !strcmp(args[0], "all");
for (int h = 0; h < RE_MAX_PLUGINS; h++) {
struct RE_plugin *p = RE_get_plugin(h);
if (!p)
continue;
int nr_hidden = 0;
sys_message("plugin %d (%s sprite=%d nr_instances=%d)\n",
h, p->plugin.name, p->sprite, p->nr_instances);
for (int i = 0; i < p->nr_instances; i++) {
struct RE_instance *inst = p->instances[i];
if (!inst)
continue;
if (!show_all && !instance_has_content(inst)) {
nr_hidden++;
continue;
}
instance_list_print(inst, i, 1);
}
if (nr_hidden)
indent_message(1, "(%d empty instance(s) hidden; use 'list all' to show)\n",
nr_hidden);
}
}
static void re_cmd_plugin(unsigned nr_args, char **args)
{
if (nr_args == 0) {
int no = dbg_resolve_plugin();
if (no < 0)
return;
struct RE_plugin *p = RE_get_plugin(no);
sys_message("plugin %d (%s)%s\n", no, p->plugin.name,
dbg_current_plugin < 0 ? " (auto-selected)" : "");
return;
}
int no = atoi(args[0]);
if (!RE_get_plugin(no)) {
DBG_ERROR("No ReignEngine plugin with handle '%s'", args[0]);
return;
}
dbg_current_plugin = no;
}
static void re_cmd_show(unsigned nr_args, char **args)
{
struct RE_plugin *p = dbg_current_re_plugin();
if (!p)
return;
cJSON *json;
if (nr_args >= 1) {
int idx = atoi(args[0]);
if (idx < 0 || idx >= p->nr_instances || !p->instances[idx]) {
DBG_ERROR("No instance with index '%s'", args[0]);
return;
}
json = instance_to_json(p->instances[idx], idx, true);
} else {
json = re_plugin_to_json(p, true);
}
char *text = cJSON_Print(json);
sys_message("%s\n", text);
free(text);
cJSON_Delete(json);
}
static void re_cmd_set(unsigned nr_args, char **args)
{
struct RE_plugin *p = dbg_current_re_plugin();
if (!p)
return;
int idx = atoi(args[0]);
if (idx < 0 || idx >= p->nr_instances || !p->instances[idx]) {
DBG_ERROR("No instance with index '%s'", args[0]);
return;
}
struct RE_instance *inst = p->instances[idx];
const char *prop = args[1];
unsigned nr_values = nr_args - 2;
char **values = &args[2];
if (!strcmp(prop, "draw")) {
if (nr_values != 1) {
DBG_ERROR("'draw' requires 1 value (0 or 1)");
return;
}
inst->draw = atoi(values[0]) != 0;
} else if (!strcmp(prop, "pos")) {
if (nr_values != 3) {
DBG_ERROR("'pos' requires 3 values (x y z)");
return;
}
inst->pos[0] = strtof(values[0], NULL);
inst->pos[1] = strtof(values[1], NULL);
inst->pos[2] = strtof(values[2], NULL);
inst->local_transform_needs_update = true;
} else {
DBG_ERROR("Unknown property '%s'", prop);
return;
}
sprite_call_plugins();
scene_render();
gfx_swap();
}
static void re_cmd_help(unsigned nr_args, char **args);
static struct dbg_cmd re_cmds[] = {
{ "list", NULL, "[all]",
"Display ReignEngine plugins and their instances (use 'all' to include uninitialized)",
0, 1, re_cmd_list },
{ "plugin", NULL, "[<plugin-no>]",
"Show or set the current plugin used by 'show' and 'set'",
0, 1, re_cmd_plugin },
{ "show", NULL, "[<instance-no>]",
"Display current plugin or instance state as JSON",
0, 1, re_cmd_show },
{ "set", NULL, "<instance-no> <property> <value...>",
"Set an instance property (draw, pos)",
3, 5, re_cmd_set },
{ "help", "h", NULL, "List 3d commands",
0, 0, re_cmd_help },
};
static void re_cmd_help(unsigned nr_args, char **args)
{
int name_len = 0;
for (unsigned i = 0; i < sizeof(re_cmds)/sizeof(*re_cmds); i++) {
int n = strlen(re_cmds[i].fullname);
if (n > name_len)
name_len = n;
}
sys_message("\n3d commands\n===========\n");
for (unsigned i = 0; i < sizeof(re_cmds)/sizeof(*re_cmds); i++) {
struct dbg_cmd *c = &re_cmds[i];
sys_message("%*s", name_len, c->fullname);
if (c->shortname)
sys_message(" (%s)", c->shortname);
if (c->arg_description)
sys_message(" %s", c->arg_description);
sys_message(" -- %s\n", c->description);
}
}
void re_debug_init(void)
{
static bool initialized = false;
if (initialized)
return;
initialized = true;
dbg_cmd_add_module("3d", sizeof(re_cmds)/sizeof(*re_cmds), re_cmds);
}
#else
void re_debug_init(void) {}
#endif // DEBUGGER_ENABLED
+84 -37
View File
@@ -54,6 +54,11 @@ struct vertex_bones {
GLfloat bone_weight[NR_WEIGHTS];
};
struct vertex_blend {
GLfloat blend_weight;
GLfloat blend_uv[2];
};
static bool is_transparent_mesh(const struct pol_mesh *mesh)
{
if (re_plugin_version == RE_REIGN_PLUGIN)
@@ -138,9 +143,11 @@ static GLuint *load_texture_list(struct archive *aar, const char *path, const ch
return textures;
}
static bool init_material(struct material *material, const struct pol_material *m, struct amt *amt, struct archive *aar, const char *path)
static bool init_material(struct material *material, struct pol_material *m, struct amt *amt, struct archive *aar, const char *path)
{
material->flags = m->flags;
glm_vec2_copy(m->uv_tiling, material->uv_tiling);
glm_vec2_one(material->blend_uv_tiling);
if (!m->textures[COLOR_MAP]) {
WARNING("No color texture");
return false;
@@ -202,6 +209,8 @@ static void destroy_material(struct material *material)
glDeleteTextures(1, &material->light_map);
if (material->normal_map)
glDeleteTextures(1, &material->normal_map);
if (material->blend_texture)
glDeleteTextures(1, &material->blend_texture);
}
static int cmp_by_bone_weight(const void *lhs, const void *rhs)
@@ -276,6 +285,7 @@ static void add_mesh(struct model *model, struct pol_mesh *m, uint32_t material_
bool has_vertex_colors = m->nr_colors > 0 || m->nr_alphas > 0;
bool has_normal_map = model->materials[material].normal_map != 0;
bool has_bones = !!model->bone_map;
bool has_blend = m->blend_weights && model->materials[material].blend_texture;
GLsizei stride = sizeof(struct vertex_common);
if (has_light_map)
@@ -286,6 +296,8 @@ static void add_mesh(struct model *model, struct pol_mesh *m, uint32_t material_
stride += sizeof(struct vertex_tangent);
if (has_bones)
stride += sizeof(struct vertex_bones);
if (has_blend)
stride += sizeof(struct vertex_blend);
void *buffer = xmalloc(m->nr_triangles * 3 * stride);
uint8_t *ptr = buffer;
@@ -336,6 +348,14 @@ static void add_mesh(struct model *model, struct pol_mesh *m, uint32_t material_
}
}
}
if (has_blend) {
struct vertex_blend *v_blend = buf_alloc(&ptr, sizeof(struct vertex_blend));
v_blend->blend_weight = m->blend_weights[t->blend_weight_index[j]];
if (m->blend_uvs)
glm_vec2_copy(m->blend_uvs[t->blend_uv_index[j]], v_blend->blend_uv);
else
glm_vec2_copy(m->uvs[t->uv_index[j]], v_blend->blend_uv);
}
nr_vertices++;
}
}
@@ -361,6 +381,8 @@ static void add_mesh(struct model *model, struct pol_mesh *m, uint32_t material_
mesh->outline_color[2] = m->edge_color.b / 255.f;
mesh->outline_thickness = m->edge_size ? m->edge_size : DEFAULT_OUTLINE_THICKNESS;
glm_vec2_copy(m->uv_scroll, mesh->uv_scroll);
glm_vec3_copy(m->specular_color, mesh->specular_color);
mesh->specular_power = m->specular_power;
mesh->nr_vertices = nr_vertices;
glGenVertexArrays(1, &mesh->vao);
@@ -412,6 +434,18 @@ static void add_mesh(struct model *model, struct pol_mesh *m, uint32_t material_
glDisableVertexAttribArray(VATTR_BONE_WEIGHT);
glVertexAttrib4f(VATTR_BONE_WEIGHT, 0.0, 0.0, 0.0, 0.0);
}
if (has_blend) {
glEnableVertexAttribArray(VATTR_BLEND_WEIGHT);
glVertexAttribPointer(VATTR_BLEND_WEIGHT, 1, GL_FLOAT, GL_FALSE, stride, base + offsetof(struct vertex_blend, blend_weight));
glEnableVertexAttribArray(VATTR_BLEND_UV);
glVertexAttribPointer(VATTR_BLEND_UV, 2, GL_FLOAT, GL_FALSE, stride, base + offsetof(struct vertex_blend, blend_uv));
base += sizeof(struct vertex_blend);
} else {
glDisableVertexAttribArray(VATTR_BLEND_WEIGHT);
glVertexAttrib1f(VATTR_BLEND_WEIGHT, 0.0);
glDisableVertexAttribArray(VATTR_BLEND_UV);
glVertexAttrib2f(VATTR_BLEND_UV, 0.0, 0.0);
}
assert((intptr_t)base == stride);
glBufferData(GL_ARRAY_BUFFER, mesh->nr_vertices * stride, buffer, GL_STATIC_DRAW);
@@ -455,14 +489,6 @@ static struct bone *add_bone(struct model *model, struct pol *pol, struct pol_bo
glm_quat_mat4(pol_bone->rotq, bone->inverse_bind_matrix);
glm_translate(bone->inverse_bind_matrix, pol_bone->pos);
// Update bone_name_map. If the bone name is not unique in the POL, set the
// map value to NULL so that ID matching will be used.
struct ht_slot *slot = ht_put(model->bone_name_map, pol_bone->name, bone);
if (slot->value != bone) {
NOTICE("%s: non-unique bone %s", model->path, pol_bone->name);
slot->value = NULL;
}
model->nr_bones++;
return bone;
}
@@ -516,11 +542,11 @@ struct model *model_load(struct archive *aar, const char *path)
if (pol->nr_bones > MAX_BONES)
ERROR("%s: Too many bones (%u)", model->path, pol->nr_bones);
model->bone_map = ht_create(pol->nr_bones * 3 / 2);
model->bone_name_map = ht_create(pol->nr_bones * 3 / 2);
model->mot_cache = ht_create(16);
model->bones = xcalloc(pol->nr_bones, sizeof(struct bone));
model->bones = xcalloc_aligned(pol->nr_bones, struct bone);
model->bones_by_pol_index = xcalloc(pol->nr_bones, sizeof(struct bone *));
for (uint32_t i = 0; i < pol->nr_bones; i++) {
add_bone(model, pol, &pol->bones[i]);
model->bones_by_pol_index[i] = add_bone(model, pol, &pol->bones[i]);
}
if (model->nr_bones != (int)pol->nr_bones)
ERROR("%s: Broken bone data", model->path);
@@ -543,12 +569,27 @@ struct model *model_load(struct archive *aar, const char *path)
continue;
}
for (uint32_t j = 0; j < pol->materials[i].nr_children; j++) {
init_material(&model->materials[material_offsets[i] + j],
&pol->materials[i].children[j], amt, aar, path);
struct pol_material_group *child = &pol->materials[i].children[j];
if (child->nr_children >= 2) {
// Group node: texture blending (base + blend)
init_material(&model->materials[material_offsets[i] + j],
&child->children[0].m, amt, aar, path);
if (child->children[1].m.textures[COLOR_MAP]) {
model->materials[material_offsets[i] + j].blend_texture =
load_texture(aar, path, child->children[1].m.textures[COLOR_MAP], NULL);
glm_vec2_copy(child->children[1].m.uv_tiling,
model->materials[material_offsets[i] + j].blend_uv_tiling);
}
} else {
init_material(&model->materials[material_offsets[i] + j],
&child->m, amt, aar, path);
}
}
}
// Meshes
struct pol_mesh **hd_meshes = xmalloc(pol->nr_meshes * sizeof(*hd_meshes));
int nr_hd_meshes = 0;
for (uint32_t i = 0; i < pol->nr_meshes; i++) {
if (!pol->meshes[i])
continue;
@@ -559,6 +600,8 @@ struct model *model_load(struct archive *aar, const char *path)
model->collider = collider_create(pol->meshes[i]);
continue;
}
if (pol->meshes[i]->flags & MESH_HEIGHT_DETECTION)
hd_meshes[nr_hd_meshes++] = pol->meshes[i];
struct pol_material_group *mg = &pol->materials[pol->meshes[i]->material];
int m_off = material_offsets[pol->meshes[i]->material];
if (mg->nr_children == 0) {
@@ -570,6 +613,10 @@ struct model *model_load(struct archive *aar, const char *path)
}
}
if (!model->collider && nr_hd_meshes > 0)
model->collider = collider_create_raycast(hd_meshes, nr_hd_meshes);
free(hd_meshes);
pol_compute_aabb(pol, model->aabb);
free(material_offsets);
@@ -594,11 +641,10 @@ void model_free(struct model *model)
for (int i = 0; i < model->nr_bones; i++)
destroy_bone(&model->bones[i]);
free(model->bones);
xfree_aligned(model->bones);
free(model->bones_by_pol_index);
if (model->bone_map)
ht_free_int(model->bone_map);
if (model->bone_name_map)
ht_free(model->bone_name_map);
if (model->mot_cache) {
ht_foreach_value(model->mot_cache, (void(*)(void*))mot_free);
ht_free(model->mot_cache);
@@ -693,6 +739,8 @@ struct model *model_create_sphere(int r, int g, int b, int a)
model->materials = xcalloc(1, sizeof(struct material));
struct material *material = &model->materials[0];
material->is_transparent = true;
glm_vec2_one(material->uv_tiling);
glm_vec2_one(material->blend_uv_tiling);
material->color_maps = xmalloc(sizeof(GLuint));
material->nr_color_maps = 1;
glGenTextures(1, material->color_maps);
@@ -705,11 +753,6 @@ struct model *model_create_sphere(int r, int g, int b, int a)
return model;
}
static int cmp_motions_by_bone_id(const void *lhs, const void *rhs)
{
return (*(struct mot_bone **)lhs)->id - (*(struct mot_bone **)rhs)->id;
}
static struct mot *mot_load(const char *name, struct model *model, struct archive *aar)
{
struct archive_data *mot_file = RE_get_aar_entry(aar, model->path, name, ".MOT");
@@ -729,24 +772,28 @@ static struct mot *mot_load(const char *name, struct model *model, struct archiv
ERROR("%s: wrong number of bones. Expected %d but got %d", name, model->nr_bones, mot->nr_bones);
// Reorder mot->motions so that motion for model->bones[i] can be
// accessed by mot->motions[i].
// accessed by mot->motions[i]. MOT bones are applied to POL bones purely
// by array index; bone name and bone id are never used for matching.
struct mot_bone **reordered = xmalloc(mot->nr_bones * sizeof(struct mot_bone *));
for (uint32_t i = 0; i < mot->nr_bones; i++) {
// Match by name first, since some MOT have wrong bone IDs (e.g. maidsan_ahoge_*).
struct bone *bone = ht_get(model->bone_name_map, mot->motions[i]->name, NULL);
// If it is not found or is NULL (non-unique bone name), match by bone ID.
if (!bone)
bone = ht_get_int(model->bone_map, mot->motions[i]->id, NULL);
if (!bone)
ERROR("%s: invalid bone \"%s\" (%d)", name, mot->motions[i]->name, mot->motions[i]->id);
mot->motions[i]->id = bone->index;
reordered[model->bones_by_pol_index[i]->index] = mot->motions[i];
}
qsort(mot->motions, mot->nr_bones, sizeof(struct mot_bone *), cmp_motions_by_bone_id);
memcpy(mot->motions, reordered, mot->nr_bones * sizeof(struct mot_bone *));
free(reordered);
// Load optional .txa file.
struct archive_data *txa_file = RE_get_aar_entry(aar, model->path, name, ".txa");
if (txa_file) {
txa_load(txa_file->data, txa_file->size, mot);
archive_free_data(txa_file);
// Load optional sidecar file.
if (re_plugin_version <= RE_TAPIR_PLUGIN) {
struct archive_data *txa_file = RE_get_aar_entry(aar, model->path, name, ".txa");
if (txa_file) {
txa_load(txa_file->data, txa_file->size, mot);
archive_free_data(txa_file);
}
} else {
struct archive_data *mpr_file = RE_get_aar_entry(aar, model->path, name, ".mpr");
if (mpr_file) {
mot->mpr = mpr_load(mpr_file->data, mpr_file->size, model);
archive_free_data(mpr_file);
}
}
return mot;
+294
View File
@@ -0,0 +1,294 @@
/* Copyright (C) 2026 kichikuou <KichikuouChrome@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <cglm/cglm.h>
#include "system4.h"
#include "3d_internal.h"
// .mpr parser.
static int resolve_mesh_index(struct model *model, const char *name)
{
for (int i = 0; i < model->nr_meshes; i++) {
if (!strcmp(model->meshes[i].name, name))
return i;
}
return -1;
}
static void append_float_key(struct mpr_track_set *ts, int frame, float v)
{
ts->mul_alpha = xrealloc(ts->mul_alpha, (ts->nr_mul_alpha + 1) * sizeof(struct mpr_float_key));
ts->mul_alpha[ts->nr_mul_alpha].frame = frame;
ts->mul_alpha[ts->nr_mul_alpha].v = v;
ts->nr_mul_alpha++;
}
static void append_vec3_key(struct mpr_vec3_key **arr, int *nr, int frame, float r, float g, float b)
{
*arr = xrealloc(*arr, (*nr + 1) * sizeof(struct mpr_vec3_key));
(*arr)[*nr].frame = frame;
(*arr)[*nr].v[0] = r;
(*arr)[*nr].v[1] = g;
(*arr)[*nr].v[2] = b;
(*nr)++;
}
static void append_int_key(struct mpr_int_key **arr, int *nr, int frame, int v)
{
*arr = xrealloc(*arr, (*nr + 1) * sizeof(struct mpr_int_key));
(*arr)[*nr].frame = frame;
(*arr)[*nr].v = v;
(*nr)++;
}
// All mpr_*_key structs have `int frame` as their first field.
static int cmp_key_by_frame(const void *a, const void *b)
{
return *(const int *)a - *(const int *)b;
}
static void sort_track_set(struct mpr_track_set *ts)
{
if (ts->nr_mul_alpha)
qsort(ts->mul_alpha, ts->nr_mul_alpha, sizeof(struct mpr_float_key), cmp_key_by_frame);
if (ts->nr_mul_diffuse)
qsort(ts->mul_diffuse, ts->nr_mul_diffuse, sizeof(struct mpr_vec3_key), cmp_key_by_frame);
if (ts->nr_add_ambient)
qsort(ts->add_ambient, ts->nr_add_ambient, sizeof(struct mpr_vec3_key), cmp_key_by_frame);
if (ts->nr_texture_anime)
qsort(ts->texture_anime, ts->nr_texture_anime, sizeof(struct mpr_int_key), cmp_key_by_frame);
}
struct mpr *mpr_load(uint8_t *data, size_t size, struct model *model)
{
struct mpr *mpr = xcalloc(1, sizeof(struct mpr));
mpr->nr_meshes = model->nr_meshes;
mpr->mesh_tracks = xcalloc(model->nr_meshes, sizeof(struct mpr_track_set *));
struct mpr_track_set *current = NULL; // current Mesh-scope track set
while (size > 0) {
char line[200];
uint8_t *nl = memchr(data, '\n', size);
if (nl)
nl++;
else
nl = data + size;
int copylen = min(nl - data, sizeof(line) - 1);
memcpy(line, (char *)data, copylen);
line[copylen] = '\0';
size -= nl - data;
data = nl;
char s[200];
int frame, ti;
float a, r, g, b;
if (sscanf(line, "Mesh = \"%[^\"]\"", s) == 1) {
int mi = resolve_mesh_index(model, s);
if (mi < 0) {
WARNING("mpr: unknown mesh \"%s\"", s);
current = NULL;
} else {
if (!mpr->mesh_tracks[mi])
mpr->mesh_tracks[mi] = xcalloc(1, sizeof(struct mpr_track_set));
current = mpr->mesh_tracks[mi];
}
} else if (sscanf(line, "MeshMulAlpha = ( %d , %f )", &frame, &a) == 2) {
if (current)
append_float_key(current, frame, a);
else
WARNING("mpr: MeshMulAlpha outside Mesh scope");
} else if (sscanf(line, "MeshMulDiffuse = ( %d , %f , %f , %f )", &frame, &r, &g, &b) == 4) {
if (current)
append_vec3_key(&current->mul_diffuse, &current->nr_mul_diffuse, frame, r, g, b);
else
WARNING("mpr: MeshMulDiffuse outside Mesh scope");
} else if (sscanf(line, "MeshAddAmbient = ( %d , %f , %f , %f )", &frame, &r, &g, &b) == 4) {
if (current)
append_vec3_key(&current->add_ambient, &current->nr_add_ambient, frame, r, g, b);
else
WARNING("mpr: MeshAddAmbient outside Mesh scope");
} else if (sscanf(line, "MeshTextureAnime = ( %d , %d )", &frame, &ti) == 2) {
if (current)
append_int_key(&current->texture_anime, &current->nr_texture_anime, frame, ti);
else
WARNING("mpr: MeshTextureAnime outside Mesh scope");
} else if (sscanf(line, "ObjectMulAlpha = ( %d , %f )", &frame, &a) == 2) {
append_float_key(&mpr->object, frame, a);
} else if (sscanf(line, "ObjectMulDiffuse = ( %d , %f , %f , %f )", &frame, &r, &g, &b) == 4) {
append_vec3_key(&mpr->object.mul_diffuse, &mpr->object.nr_mul_diffuse, frame, r, g, b);
} else if (sscanf(line, "ObjectAddAmbient = ( %d , %f , %f , %f )", &frame, &r, &g, &b) == 4) {
append_vec3_key(&mpr->object.add_ambient, &mpr->object.nr_add_ambient, frame, r, g, b);
} else if (strchr(line, '=')) {
WARNING("mpr: unknown field: %s", line);
}
}
for (int i = 0; i < mpr->nr_meshes; i++) {
if (!mpr->mesh_tracks[i])
continue;
sort_track_set(mpr->mesh_tracks[i]);
if (mpr->mesh_tracks[i]->nr_mul_alpha > 0)
mpr->has_mesh_alpha = true;
}
sort_track_set(&mpr->object);
return mpr;
}
static void destroy_track_set(struct mpr_track_set *ts)
{
free(ts->mul_alpha);
free(ts->mul_diffuse);
free(ts->add_ambient);
free(ts->texture_anime);
}
void mpr_free(struct mpr *mpr)
{
if (!mpr)
return;
for (int i = 0; i < mpr->nr_meshes; i++) {
if (mpr->mesh_tracks[i]) {
destroy_track_set(mpr->mesh_tracks[i]);
free(mpr->mesh_tracks[i]);
}
}
free(mpr->mesh_tracks);
destroy_track_set(&mpr->object);
free(mpr);
}
// Keyframe evaluation.
// Returns the largest index i with keys[i].frame <= frame.
// Returns -1 if frame < keys[0].frame.
#define DEFINE_FIND_KEY(NAME, KEY_TYPE) \
static int NAME(const KEY_TYPE *keys, int nr, float frame) \
{ \
if (frame < (float)keys[0].frame) \
return -1; \
int lo = 0, hi = nr - 1; \
while (lo < hi) { \
int mid = (lo + hi + 1) / 2; \
if ((float)keys[mid].frame <= frame) \
lo = mid; \
else \
hi = mid - 1; \
} \
return lo; \
}
DEFINE_FIND_KEY(find_float_key, struct mpr_float_key)
DEFINE_FIND_KEY(find_vec3_key, struct mpr_vec3_key)
DEFINE_FIND_KEY(find_int_key, struct mpr_int_key)
static float eval_float(const struct mpr_float_key *keys, int nr, float frame, float fallback)
{
if (nr == 0)
return fallback;
int i = find_float_key(keys, nr, frame);
if (i < 0)
return keys[0].v;
if (i >= nr - 1)
return keys[nr - 1].v;
float t = (frame - keys[i].frame) / (float)(keys[i + 1].frame - keys[i].frame);
return glm_lerp(keys[i].v, keys[i + 1].v, t);
}
static void eval_vec3(const struct mpr_vec3_key *keys, int nr, float frame, const vec3 fallback, vec3 out)
{
if (nr == 0) {
glm_vec3_copy((float *)fallback, out);
return;
}
int i = find_vec3_key(keys, nr, frame);
if (i < 0) {
glm_vec3_copy((float *)keys[0].v, out);
return;
}
if (i >= nr - 1) {
glm_vec3_copy((float *)keys[nr - 1].v, out);
return;
}
float t = (frame - keys[i].frame) / (float)(keys[i + 1].frame - keys[i].frame);
glm_vec3_lerp((float *)keys[i].v, (float *)keys[i + 1].v, t, out);
}
static int eval_int(const struct mpr_int_key *keys, int nr, float frame, int fallback)
{
if (nr == 0)
return fallback;
int i = find_int_key(keys, nr, frame);
if (i < 0)
return keys[0].v;
return keys[i].v; // step
}
// Combine inst defaults with .mpr Object-scope keyframes.
void mpr_evaluate_object(const struct mpr *mpr, float frame,
struct RE_instance *inst, struct mpr_modulation *out)
{
out->alpha = inst->alpha;
glm_vec3_copy(inst->ambient, out->ambient);
glm_vec3_copy(inst->diffuse, out->diffuse);
if (!mpr)
return;
out->alpha *= eval_float(mpr->object.mul_alpha, mpr->object.nr_mul_alpha, frame, 1.f);
vec3 add;
eval_vec3(mpr->object.add_ambient, mpr->object.nr_add_ambient, frame, (vec3){0,0,0}, add);
glm_vec3_add(out->ambient, add, out->ambient);
eval_vec3(mpr->object.mul_diffuse, mpr->object.nr_mul_diffuse, frame, (vec3){1,1,1}, out->diffuse);
}
// Combine the object-scope values with .mpr Mesh-scope keyframes for one mesh.
void mpr_evaluate_mesh(const struct mpr_track_set *mt, float frame,
const struct mpr_modulation *obj, struct mpr_modulation *out)
{
*out = *obj;
if (!mt)
return;
out->alpha *= eval_float(mt->mul_alpha, mt->nr_mul_alpha, frame, 1.f);
vec3 add;
eval_vec3(mt->add_ambient, mt->nr_add_ambient, frame, (vec3){0,0,0}, add);
glm_vec3_add(out->ambient, add, out->ambient);
vec3 mul;
eval_vec3(mt->mul_diffuse, mt->nr_mul_diffuse, frame, (vec3){1,1,1}, mul);
glm_vec3_mul(out->diffuse, mul, out->diffuse);
}
// MeshTextureAnime writes to the shared material slot, so all meshes
// referencing the same material pick up the animation even if .mpr only names
// one of them. Caller supplies the output buffer sized to model->nr_materials.
void mpr_build_mat_tex_index(const struct model *model, const struct RE_instance *inst,
const struct mpr *mpr, float frame, int *mat_tex_index)
{
for (int m = 0; m < model->nr_materials; m++)
mat_tex_index[m] = inst->texture_animation_index;
if (!mpr)
return;
for (int i = 0; i < mpr->nr_meshes; i++) {
const struct mpr_track_set *t = mpr->mesh_tracks[i];
if (!t || t->nr_texture_anime == 0)
continue;
int mat = model->meshes[i].material;
mat_tex_index[mat] = eval_int(t->texture_anime, t->nr_texture_anime, frame, mat_tex_index[mat]);
}
}
+196 -22
View File
@@ -70,17 +70,22 @@ static uint32_t parse_material_attributes(const char *name)
return flags;
}
static void parse_material(struct buffer *r, struct pol_material *m)
static void parse_textures(struct buffer *r, int pol_version, struct pol_material *m)
{
m->name = read_cstring(r);
m->flags = parse_material_attributes(m->name);
glm_vec2_one(m->uv_tiling);
int nr_textures = buffer_read_int32(r);
for (int i = 0; i < nr_textures; i++) {
char *filename = read_cstring(r);
int type = buffer_read_int32(r);
vec2 uv_tiling = { 1.0f, 1.0f };
if (pol_version >= 3) {
uv_tiling[0] = buffer_read_float(r);
uv_tiling[1] = buffer_read_float(r);
}
if ((unsigned)type < MAX_TEXTURE_TYPE) {
m->textures[type] = filename;
if (type == COLOR_MAP)
glm_vec2_copy(uv_tiling, m->uv_tiling);
} else {
WARNING("invalid texture type %d", type);
free(filename);
@@ -95,29 +100,60 @@ static void destroy_material(struct pol_material *m)
free(m->textures[i]);
}
static void parse_material_group(struct buffer *r, struct pol_material_group *mg)
{
parse_material(r, &mg->m);
mg->nr_children = buffer_read_int32(r);
if (mg->nr_children > 0) {
mg->children = xcalloc(mg->nr_children, sizeof(struct pol_material));
for (uint32_t i = 0; i < mg->nr_children; i++) {
parse_material(r, &mg->children[i]);
}
}
}
static void destroy_material_group(struct pol_material_group *mg)
{
destroy_material(&mg->m);
if (mg->children) {
for (uint32_t i = 0; i < mg->nr_children; i++)
destroy_material(&mg->children[i]);
destroy_material_group(&mg->children[i]);
free(mg->children);
}
}
// Material tree layout:
// level 0: top-level material per pol->materials[]. Either a textured leaf
// or a group containing sub-materials, never both.
// level 1: sub-material. Either a textured leaf or, in v4 only, a "blend
// group" (is_group=1) with exactly 2 leaf children.
// level 2: leaf children of a v4 blend group; cannot themselves be a group.
static void parse_material_group(struct buffer *r, int pol_version,
struct pol_material_group *mg, int level)
{
mg->m.name = read_cstring(r);
mg->m.flags = parse_material_attributes(mg->m.name);
bool is_group = false;
if (pol_version >= 4)
is_group = buffer_read_int32(r);
if (is_group) {
if (level >= 2)
ERROR("material group nesting too deep");
uint32_t uk = buffer_read_int32(r);
if (uk != 0)
ERROR("unexpected nonzero value in material group: %u", uk);
mg->nr_children = buffer_read_int32(r);
if (level == 1 && mg->nr_children != 2)
ERROR("blend group must have 2 children, got %u", mg->nr_children);
} else {
parse_textures(r, pol_version, &mg->m);
if (pol_version >= 4) {
uint32_t uk = buffer_read_int32(r);
if (uk != 0)
ERROR("unexpected nonzero value in material leaf: %u", uk);
} else if (level == 0) {
mg->nr_children = buffer_read_int32(r);
}
}
if (mg->nr_children > 0) {
mg->children = xcalloc(mg->nr_children, sizeof(struct pol_material_group));
for (uint32_t i = 0; i < mg->nr_children; i++)
parse_material_group(r, pol_version, &mg->children[i], level + 1);
}
}
static void parse_vertex(struct buffer *r, int pol_version, struct pol_vertex *v)
{
read_position(r, v->pos);
@@ -151,6 +187,11 @@ static void parse_triangle(struct buffer *r, struct pol_mesh *mesh, int triangle
t->light_uv_index[1] = buffer_read_int32(r) - mesh->nr_uvs;
t->light_uv_index[2] = buffer_read_int32(r) - mesh->nr_uvs;
}
if (mesh->blend_uvs) {
t->blend_uv_index[0] = buffer_read_int32(r) - mesh->nr_uvs - mesh->nr_light_uvs;
t->blend_uv_index[1] = buffer_read_int32(r) - mesh->nr_uvs - mesh->nr_light_uvs;
t->blend_uv_index[2] = buffer_read_int32(r) - mesh->nr_uvs - mesh->nr_light_uvs;
}
t->color_index[0] = buffer_read_int32(r);
t->color_index[1] = buffer_read_int32(r);
@@ -160,6 +201,11 @@ static void parse_triangle(struct buffer *r, struct pol_mesh *mesh, int triangle
t->alpha_index[1] = buffer_read_int32(r);
t->alpha_index[2] = buffer_read_int32(r);
}
if (mesh->blend_weights) {
t->blend_weight_index[0] = buffer_read_int32(r);
t->blend_weight_index[1] = buffer_read_int32(r);
t->blend_weight_index[2] = buffer_read_int32(r);
}
read_direction(r, t->normals[0]);
read_direction(r, t->normals[1]);
@@ -227,6 +273,17 @@ static struct pol_mesh *parse_mesh(struct buffer *r, const struct pol *pol)
}
}
if (pol->version >= 4) {
mesh->nr_blend_uvs = buffer_read_int32(r);
if (mesh->nr_blend_uvs > 0) {
mesh->blend_uvs = xcalloc(mesh->nr_blend_uvs, sizeof(vec2));
for (uint32_t i = 0; i < mesh->nr_blend_uvs; i++) {
mesh->blend_uvs[i][0] = buffer_read_float(r);
mesh->blend_uvs[i][1] = buffer_read_float(r);
}
}
}
mesh->nr_colors = buffer_read_int32(r);
if (mesh->nr_colors > 0) {
mesh->colors = xcalloc(mesh->nr_colors, sizeof(vec3));
@@ -254,6 +311,16 @@ static struct pol_mesh *parse_mesh(struct buffer *r, const struct pol *pol)
}
}
if (pol->version >= 4) {
mesh->nr_blend_weights = buffer_read_int32(r);
if (mesh->nr_blend_weights > 0) {
mesh->blend_weights = xcalloc(mesh->nr_blend_weights, sizeof(float));
for (uint32_t i = 0; i < mesh->nr_blend_weights; i++) {
mesh->blend_weights[i] = buffer_read_u8(r) / 255.f;
}
}
}
mesh->nr_triangles = buffer_read_int32(r);
mesh->triangles = xcalloc(mesh->nr_triangles, sizeof(struct pol_triangle));
for (uint32_t i = 0; i < mesh->nr_triangles; i++) {
@@ -280,8 +347,10 @@ static void free_mesh(struct pol_mesh *mesh)
free(mesh->vertices);
free(mesh->uvs);
free(mesh->light_uvs);
free(mesh->blend_uvs);
free(mesh->colors);
free(mesh->alphas);
free(mesh->blend_weights);
free(mesh->triangles);
free(mesh);
}
@@ -310,7 +379,7 @@ struct pol *pol_parse(uint8_t *data, size_t size)
struct pol *pol = xcalloc(1, sizeof(struct pol));
pol->version = buffer_read_int32(&r);
if (pol->version != 1 && pol->version != 2) {
if (pol->version != 1 && pol->version != 2 && pol->version != 4) {
WARNING("unknown POL version: %d", pol->version);
free(pol);
return NULL;
@@ -318,7 +387,7 @@ struct pol *pol_parse(uint8_t *data, size_t size)
pol->nr_materials = buffer_read_int32(&r);
pol->materials = xcalloc(pol->nr_materials, sizeof(struct pol_material_group));
for (uint32_t i = 0; i < pol->nr_materials; i++) {
parse_material_group(&r, &pol->materials[i]);
parse_material_group(&r, pol->version, &pol->materials[i], 0);
}
pol->nr_meshes = buffer_read_int32(&r);
@@ -428,6 +497,7 @@ void mot_free(struct mot *mot)
if (mot->texture_indices) {
free(mot->texture_indices);
}
mpr_free(mot->mpr);
free(mot);
}
@@ -510,7 +580,7 @@ void opr_load(uint8_t *data, size_t size, struct pol *pol)
char s[200];
int i1, i2, i3;
float f1, f2;
float f1, f2, f3;
if (sscanf(line, "Mesh = \"%[^\"]\"", s) == 1) {
for (int i = 0; i < pol->nr_meshes; i++) {
selected[i] = pol->meshes[i] && !strcmp(pol->meshes[i]->name, s);
@@ -548,7 +618,12 @@ void opr_load(uint8_t *data, size_t size, struct pol *pol)
pol->meshes[i]->edge_size = f1;
}
} else if (sscanf(line, "HeightDetection = %s", s) == 1) {
if (!strcmp(s, "false")) {
if (!strcmp(s, "true")) {
for (int i = 0; i < pol->nr_meshes; i++) {
if (!selected[i]) continue;
pol->meshes[i]->flags |= MESH_HEIGHT_DETECTION;
}
} else if (!strcmp(s, "false")) {
for (int i = 0; i < pol->nr_meshes; i++) {
if (!selected[i]) continue;
pol->meshes[i]->flags |= MESH_NO_HEIGHT_DETECTION;
@@ -556,12 +631,51 @@ void opr_load(uint8_t *data, size_t size, struct pol *pol)
} else {
WARNING("invalid HeightDetection: %s", s);
}
} else if (sscanf(line, "DrawShadow = %s", s) == 1) {
if (!strcmp(s, "false")) {
for (int i = 0; i < pol->nr_meshes; i++) {
if (!selected[i]) continue;
pol->meshes[i]->flags |= MESH_NO_DRAWSHADOW;
}
} else if (strcmp(s, "true")) {
WARNING("invalid DrawShadow: %s", s);
}
} else if (sscanf(line, "ZWrite = %s", s) == 1) {
if (!strcmp(s, "false")) {
for (int i = 0; i < pol->nr_meshes; i++) {
if (!selected[i]) continue;
pol->meshes[i]->flags |= MESH_NO_ZWRITE;
}
} else if (strcmp(s, "true")) {
WARNING("invalid ZWrite: %s", s);
}
} else if (sscanf(line, "UVScroll = ( %f , %f )", &f1, &f2) == 2) {
for (int i = 0; i < pol->nr_meshes; i++) {
if (!selected[i]) continue;
pol->meshes[i]->uv_scroll[0] = f1;
pol->meshes[i]->uv_scroll[1] = f2;
}
} else if (sscanf(line, "SpecularColor = ( %f , %f , %f )", &f1, &f2, &f3) == 3) {
for (int i = 0; i < pol->nr_meshes; i++) {
if (!selected[i]) continue;
pol->meshes[i]->flags |= MESH_HAS_SPECULAR_COLOR;
pol->meshes[i]->specular_color[0] = f1;
pol->meshes[i]->specular_color[1] = f2;
pol->meshes[i]->specular_color[2] = f3;
}
} else if (sscanf(line, "SpecularPower = %f", &f1) == 1) {
for (int i = 0; i < pol->nr_meshes; i++) {
if (!selected[i]) continue;
pol->meshes[i]->flags |= MESH_HAS_SPECULAR_POWER;
pol->meshes[i]->specular_power = f1;
}
} else if (sscanf(line, "ParallaxScale = %f", &f1) == 1) {
// parallax mapping is not implemented
} else if (sscanf(line, "ReliefScale = %f", &f1) == 1) {
// relief mapping is not implemented
} else if (sscanf(line, "MeshCombinable = %s", s) == 1) {
if (strcmp(s, "true") && strcmp(s, "false"))
WARNING("invalid MeshCombinable: %s", s);
} else if (strchr(line, '=')) {
WARNING("unknown field: %s", line);
}
@@ -595,3 +709,63 @@ void txa_load(uint8_t *data, size_t size, struct mot *mot)
mot->nr_texture_indices = count;
mot->texture_indices = indices;
}
void lit_reset(float *out)
{
static const float lit_param_defaults[RE_NR_LIGHT_PARAMS] = {
[SEAL_LP_TONEMAP_EXPOSURE_BIAS] = 1.0f,
[SEAL_LP_TONEMAP_WHITE_POINT] = 1.0f,
[SEAL_LP_TONEMAP_A] = 0.22f,
[SEAL_LP_TONEMAP_B] = 0.30f,
[SEAL_LP_TONEMAP_C] = 0.10f,
[SEAL_LP_TONEMAP_D] = 0.20f,
[SEAL_LP_TONEMAP_E] = 0.01f,
[SEAL_LP_TONEMAP_F] = 0.30f,
[SEAL_LP_HEMI_VEC + 0] = -1.0f,
[SEAL_LP_HEMI_VEC + 1] = -1.0f,
[SEAL_LP_HEMI_VEC + 2] = 1.0f,
[SEAL_LP_HEMI_SKY_COLOR + 0] = 1.2f,
[SEAL_LP_HEMI_SKY_COLOR + 1] = 1.2f,
[SEAL_LP_HEMI_SKY_COLOR + 2] = 1.2f,
[SEAL_LP_HEMI_MID_COLOR + 0] = 0.5f,
[SEAL_LP_HEMI_MID_COLOR + 1] = 0.5f,
[SEAL_LP_HEMI_MID_COLOR + 2] = 0.5f,
[SEAL_LP_HEMI_GROUND_COLOR + 0] = 0.3f,
[SEAL_LP_HEMI_GROUND_COLOR + 1] = 0.3f,
[SEAL_LP_HEMI_GROUND_COLOR + 2] = 0.3f,
[SEAL_LP_LS_BETA_R] = 0.25f,
[SEAL_LP_LS_BETA_M] = 1.25f,
[SEAL_LP_LS_G] = 0.05f,
[SEAL_LP_LS_DISTANCE] = 140.0f,
[SEAL_LP_LS_LIGHT_VEC + 0] = -1.0f,
[SEAL_LP_LS_LIGHT_VEC + 1] = -1.0f,
[SEAL_LP_LS_LIGHT_VEC + 2] = 1.0f,
[SEAL_LP_LS_LIGHT_COLOR + 0] = 1.0f,
[SEAL_LP_LS_LIGHT_COLOR + 1] = 1.0f,
[SEAL_LP_LS_LIGHT_COLOR + 2] = 1.0f,
[SEAL_LP_LS_SUN_COLOR + 0] = 0.5f,
[SEAL_LP_LS_SUN_COLOR + 1] = 0.5f,
[SEAL_LP_LS_SUN_COLOR + 2] = 0.5f,
};
memcpy(out, lit_param_defaults, sizeof(lit_param_defaults));
}
bool lit_parse(uint8_t *data, size_t size, float *out)
{
if (size != 8 + RE_NR_LIGHT_PARAMS * 4 || memcmp(data, "LITP", 4) != 0) {
WARNING("invalid .lit file");
return false;
}
struct buffer r;
buffer_init(&r, data, size);
buffer_skip(&r, 4); // "LITP"
int32_t version = buffer_read_int32(&r);
if (version != 0) {
WARNING("unsupported .lit version %d", version);
return false;
}
lit_reset(out);
for (int i = 0; i < RE_NR_LIGHT_PARAMS; i++)
out[i] = buffer_read_float(&r);
return true;
}
+315 -56
View File
@@ -23,6 +23,7 @@
#include "system4.h"
#include "system4/aar.h"
#include "system4/archive.h"
#include "system4/cg.h"
#include "system4/hashtable.h"
#include "system4/string.h"
@@ -38,19 +39,28 @@ enum RE_plugin_version re_plugin_version;
static struct RE_instance *create_instance(struct RE_plugin *plugin)
{
struct RE_instance *instance = xcalloc(1, sizeof(struct RE_instance));
struct RE_instance *instance = xcalloc_aligned(1, struct RE_instance);
instance->plugin = plugin;
instance->draw_edge = plugin->version == RE_TAPIR_PLUGIN;
for (int i = 0; i < RE_NR_INSTANCE_TARGETS; i++)
instance->target[i] = -1;
instance->scale[0] = 1.0f;
instance->scale[1] = 1.0f;
instance->scale[2] = 1.0f;
glm_vec3_one(instance->scale);
instance->alpha = 1.0f;
glm_vec3_one(instance->diffuse);
instance->shadow_volume_bone_radius = 0.1f;
instance->draw_bump = true;
instance->fps = 30.0f;
instance->use_mag_speed = true;
instance->column_height = 1.0f;
instance->column_radius = 1.0f;
glm_vec3_copy((vec3){-1.0f, 2.0f, 0.0f}, instance->vertex_pos[0]);
glm_vec3_copy((vec3){-1.0f, 0.0f, 0.0f}, instance->vertex_pos[1]);
glm_vec3_copy((vec3){ 1.0f, 2.0f, 0.0f}, instance->vertex_pos[2]);
glm_vec3_copy((vec3){ 1.0f, 0.0f, 0.0f}, instance->vertex_pos[3]);
glm_vec2_copy((vec2){0.0f, 0.0f}, instance->vertex_uv[0]);
glm_vec2_copy((vec2){0.0f, 1.0f}, instance->vertex_uv[1]);
glm_vec2_copy((vec2){1.0f, 0.0f}, instance->vertex_uv[2]);
glm_vec2_copy((vec2){1.0f, 1.0f}, instance->vertex_uv[3]);
glm_mat4_identity(instance->local_transform);
glm_mat3_identity(instance->normal_transform);
return instance;
@@ -60,7 +70,13 @@ static void free_instance(struct RE_instance *instance);
static void unload_instance(struct RE_instance *instance)
{
if (instance->motion) {
if (instance->name) {
free_string(instance->name);
instance->name = NULL;
}
// A billboard's motion holds frame animation state that the game can set
// before loading the instance data, so it must survive unloads.
if (instance->motion && instance->type != RE_ITYPE_BILLBOARD) {
motion_free(instance->motion);
instance->motion = NULL;
}
@@ -76,11 +92,21 @@ static void unload_instance(struct RE_instance *instance)
particle_effect_free(instance->effect);
instance->effect = NULL;
}
if (instance->s3de_effect) {
s3de_effect_free(instance->s3de_effect);
instance->s3de_effect = NULL;
}
if (instance->bone_transforms) {
glDeleteBuffers(1, &instance->bone_transforms_ubo);
free(instance->bone_transforms);
instance->bone_transforms = NULL;
}
if (instance->billboard_frames) {
// No need to free the textures, they are owned by the renderer.
free(instance->billboard_frames);
instance->billboard_frames = NULL;
instance->nr_billboard_frames = 0;
}
if (instance->height_detector) {
RE_renderer_free_height_detector(instance->height_detector);
instance->height_detector = NULL;
@@ -94,7 +120,10 @@ static void unload_instance(struct RE_instance *instance)
static void free_instance(struct RE_instance *instance)
{
unload_instance(instance);
free(instance);
if (instance->motion)
motion_free(instance->motion);
free(instance->light_params);
xfree_aligned(instance);
}
static void RE_back_cg_init(struct RE_back_cg *bcg)
@@ -215,9 +244,10 @@ static void update_bones(struct RE_instance *inst)
struct RE_plugin *RE_plugin_new(enum RE_plugin_version version)
{
re_debug_init();
re_plugin_version = version;
char *aar_path = gamedir_path("Data/ReignData.red");
char *aar_path = gamedir_path(version <= RE_TAPIR_PLUGIN ? "Data/ReignData.red" : "Data/3DData.red");
int error = ARCHIVE_SUCCESS;
struct archive *aar = (struct archive *)aar_open(aar_path, MMAP_IF_64BIT, &error);
if (error == ARCHIVE_FILE_ERROR) {
@@ -229,7 +259,8 @@ struct RE_plugin *RE_plugin_new(enum RE_plugin_version version)
if (!aar)
return NULL;
struct RE_plugin *plugin = xcalloc(1, sizeof(struct RE_plugin));
struct RE_plugin *plugin = xcalloc_aligned(1, struct RE_plugin);
plugin->version = version;
plugin->plugin.name = "ReignEngine";
plugin->plugin.update = RE_render;
plugin->plugin.to_json = RE_to_json;
@@ -237,18 +268,25 @@ struct RE_plugin *RE_plugin_new(enum RE_plugin_version version)
plugin->instances = xcalloc(plugin->nr_instances, sizeof(struct RE_instance *));
plugin->aar = aar;
plugin->model_cache = ht_create(32);
plugin->pae_cache = ht_create(32);
plugin->effect_cache = ht_create(32);
for (int i = 0; i < RE_NR_BACK_CGS; i++)
RE_back_cg_init(&plugin->back_cg[i]);
plugin->mag_speed = 1;
if (version == RE_TAPIR_PLUGIN)
plugin->draw_options[RE_DRAW_OPTION_EDGE] = 1;
plugin->draw_options[RE_DRAW_OPTION_LIGHTING] = 1;
plugin->edge_length = 0.02f;
plugin->fog_type = RE_FOG_NONE;
plugin->fog_near = 1.0;
plugin->fog_far = 10.0;
plugin->fog_near = 1.0f;
plugin->fog_far = 10.0f;
glm_vec3_one(plugin->fog_color);
lit_reset(plugin->light_params);
return plugin;
}
void RE_plugin_free(struct RE_plugin *plugin)
{
RE_plugin_unbind(plugin);
for (int i = 0; i < plugin->nr_instances; i++) {
if (plugin->instances[i])
free_instance(plugin->instances[i]);
@@ -257,13 +295,12 @@ void RE_plugin_free(struct RE_plugin *plugin)
archive_free(plugin->aar);
ht_foreach_value(plugin->model_cache, (void(*)(void*))model_free);
ht_free(plugin->model_cache);
ht_foreach_value(plugin->pae_cache, (void(*)(void*))pae_free);
ht_free(plugin->pae_cache);
if (plugin->renderer)
RE_renderer_free(plugin->renderer);
ht_foreach_value(plugin->effect_cache, re_plugin_version < RE_SEAL_PLUGIN ?
(void(*)(void*))pae_free : (void(*)(void*))s3de_free);
ht_free(plugin->effect_cache);
for (int i = 0; i < RE_NR_BACK_CGS; i++)
RE_back_cg_destroy(&plugin->back_cg[i]);
free(plugin);
xfree_aligned(plugin);
}
bool RE_plugin_bind(struct RE_plugin *plugin, int sprite)
@@ -320,6 +357,7 @@ bool RE_build_model(struct RE_plugin *plugin, int elapsed_ms)
return false;
plugin->camera.quake_pitch = plugin->camera.quake_yaw = 0.0;
plugin->camera.override_active = false;
for (int i = 0; i < plugin->nr_instances; i++) {
struct RE_instance *inst = plugin->instances[i];
@@ -327,6 +365,8 @@ bool RE_build_model(struct RE_plugin *plugin, int elapsed_ms)
continue;
float delta_frame = inst->fps * elapsed_ms / 1000.0;
if (inst->use_mag_speed)
delta_frame *= plugin->mag_speed;
update_motion(inst->motion, delta_frame);
update_motion(inst->next_motion, delta_frame);
@@ -339,7 +379,11 @@ bool RE_build_model(struct RE_plugin *plugin, int elapsed_ms)
// update_bones().
for (int i = 0; i < plugin->nr_instances; i++) {
struct RE_instance *inst = plugin->instances[i];
if (inst && inst->type == RE_ITYPE_PARTICLE_EFFECT)
if (!inst || inst->type != RE_ITYPE_PARTICLE_EFFECT)
continue;
if (inst->s3de_effect)
s3de_effect_update(inst);
else
particle_effect_update(inst);
}
return true;
@@ -357,6 +401,10 @@ bool RE_set_viewport(struct RE_plugin *plugin, int x, int y, int width, int heig
sprite_init_color(sp, width, height, 0, 0, 0, 255);
sprite_set_pos(sp, x, y);
RE_renderer_set_viewport_size(plugin->renderer, width, height);
plugin->viewport_x = x;
plugin->viewport_y = y;
plugin->viewport_width = width;
plugin->viewport_height = height;
return true;
}
@@ -380,17 +428,29 @@ int RE_create_instance(struct RE_plugin *plugin)
{
if (!plugin)
return -1;
for (int i = 0; i < plugin->nr_instances; i++) {
if (!plugin->instances[i]) {
plugin->instances[i] = create_instance(plugin);
return i;
}
int index;
for (index = 0; index < plugin->nr_instances; index++) {
if (!plugin->instances[index])
break;
}
int old_size = plugin->nr_instances;
plugin->nr_instances *= 2;
plugin->instances = xrealloc_array(plugin->instances, old_size, plugin->nr_instances, sizeof(struct RE_instance *));
plugin->instances[old_size] = create_instance(plugin);
return old_size;
RE_create_instance_at(plugin, index);
return index;
}
struct RE_instance *RE_create_instance_at(struct RE_plugin *plugin, int index)
{
if (index >= plugin->nr_instances) {
int old_size = plugin->nr_instances;
int new_size = old_size;
while (new_size <= index)
new_size *= 2;
plugin->instances = xrealloc_array(plugin->instances, old_size, new_size, sizeof(struct RE_instance *));
plugin->nr_instances = new_size;
}
if (plugin->instances[index])
free_instance(plugin->instances[index]);
plugin->instances[index] = create_instance(plugin);
return plugin->instances[index];
}
bool RE_release_instance(struct RE_plugin *plugin, int instance)
@@ -419,6 +479,10 @@ bool RE_instance_set_type(struct RE_instance *instance, int type)
instance->motion->instance = instance;
break;
case RE_ITYPE_DIRECTIONAL_LIGHT:
if (re_plugin_version >= RE_SEAL_PLUGIN) {
instance->light_params = xmalloc(RE_NR_LIGHT_PARAMS * sizeof(float));
lit_reset(instance->light_params);
}
break;
case RE_ITYPE_SPECULAR_LIGHT:
break;
@@ -433,15 +497,71 @@ bool RE_instance_set_type(struct RE_instance *instance, int type)
return true;
}
bool RE_instance_data_exists(struct RE_instance *instance, const char *name)
{
if (!instance)
return false;
const char *ext;
switch (instance->type) {
case RE_ITYPE_STATIC:
case RE_ITYPE_SKINNED:
ext = ".POL";
break;
case RE_ITYPE_PARTICLE_EFFECT:
ext = ".3de";
break;
default:
return archive_exists_by_name(instance->plugin->aar, name, NULL);
}
const char *basename = strrchr(name, '\\');
basename = basename ? basename + 1 : name;
char *path = xmalloc(strlen(name) + strlen(basename) + strlen(ext) + 2);
sprintf(path, "%s\\%s%s", name, basename, ext);
bool exists = archive_exists_by_name(instance->plugin->aar, path, NULL);
free(path);
return exists;
}
// Loads the frame images of a billboard instance. They are stored in the
// archive as "<name>\<basename>.png", "<name>\<basename>[1].png", ...
static bool load_billboard_frames(struct RE_instance *instance, const char *name)
{
const char *basename = strrchr(name, '\\');
basename = basename ? basename + 1 : name;
char *path = xmalloc(strlen(name) + strlen(basename) + 22);
int capacity = 8;
instance->billboard_frames = xcalloc(capacity, sizeof(struct billboard_texture *));
for (int i = 0;; i++) {
if (i == 0)
sprintf(path, "%s\\%s.png", name, basename);
else
sprintf(path, "%s\\%s[%d].png", name, basename, i);
struct billboard_texture *bt = RE_renderer_load_billboard_texture_by_path(
instance->plugin->renderer, instance->plugin->aar, path);
if (!bt)
break;
if (i == capacity) {
instance->billboard_frames = xrealloc_array(
instance->billboard_frames, capacity, capacity * 2, sizeof(struct billboard_texture *));
capacity *= 2;
}
instance->billboard_frames[i] = bt;
instance->nr_billboard_frames = i + 1;
}
free(path);
return instance->nr_billboard_frames > 0;
}
bool RE_instance_load(struct RE_instance *instance, const char *name)
{
if (!instance)
return false;
unload_instance(instance);
if (name[0] == '\0')
return false;
return true; // empty name just unloads the instance
instance->name = cstr_to_string(name);
struct pae *pae;
switch (instance->type) {
case RE_ITYPE_STATIC:
case RE_ITYPE_SKINNED:
@@ -460,26 +580,68 @@ bool RE_instance_load(struct RE_instance *instance, const char *name)
}
return !!instance->model;
case RE_ITYPE_PARTICLE_EFFECT:
pae = ht_get(instance->plugin->pae_cache, name, NULL);
if (!pae) {
pae = pae_load(instance->plugin->aar, name);
if (!pae)
return false;
ht_put(instance->plugin->pae_cache, name, pae);
}
instance->effect = particle_effect_create(pae);
if (!instance->motion) {
instance->motion = xcalloc(1, sizeof(struct motion));
instance->motion->instance = instance;
pae_calc_frame_range(instance->effect->pae, instance->motion);
if (re_plugin_version >= RE_SEAL_PLUGIN) {
struct s3de *s3de = ht_get(instance->plugin->effect_cache, name, NULL);
if (!s3de) {
s3de = s3de_load(instance->plugin->aar, name);
if (!s3de)
return false;
ht_put(instance->plugin->effect_cache, name, s3de);
}
instance->s3de_effect = s3de_effect_create(s3de, instance->plugin->aar);
if (!instance->motion) {
instance->motion = xcalloc(1, sizeof(struct motion));
instance->motion->instance = instance;
s3de_calc_frame_range(instance->s3de_effect->s3de, instance->motion);
}
} else {
struct pae *pae = ht_get(instance->plugin->effect_cache, name, NULL);
if (!pae) {
pae = pae_load(instance->plugin->aar, name);
if (!pae)
return false;
ht_put(instance->plugin->effect_cache, name, pae);
}
instance->effect = particle_effect_create(pae);
if (!instance->motion) {
instance->motion = xcalloc(1, sizeof(struct motion));
instance->motion->instance = instance;
pae_calc_frame_range(instance->effect->pae, instance->motion);
}
}
return true;
case RE_ITYPE_BILLBOARD:
if (re_plugin_version >= RE_SEAL_PLUGIN)
return load_billboard_frames(instance, name);
case RE_ITYPE_DIRECTIONAL_LIGHT:
if (re_plugin_version >= RE_SEAL_PLUGIN) {
struct archive_data *dfile = archive_get_by_name(instance->plugin->aar, name);
if (!dfile)
return false;
bool ok = lit_parse(dfile->data, dfile->size, instance->light_params);
archive_free_data(dfile);
return ok;
}
return false;
default:
WARNING("Invalid instance type %d", instance->type);
return false;
}
}
bool RE_instance_motion_exists(struct RE_instance *instance, const char *name)
{
if (!instance || !instance->model)
return false;
if (instance->model->mot_cache && ht_get(instance->model->mot_cache, name, NULL))
return true;
char *path = xmalloc(strlen(instance->model->path) + strlen(name) + 6);
sprintf(path, "%s\\%s.MOT", instance->model->path, name);
bool exists = archive_exists_by_name(instance->plugin->aar, path, NULL);
free(path);
return exists;
}
bool RE_instance_load_motion(struct RE_instance *instance, const char *name)
{
if (!instance)
@@ -522,19 +684,28 @@ bool RE_instance_set_mesh_show(struct RE_instance *instance, const char *mesh_na
return false;
}
// Converts from the (top-left, top-right, bottom-left, bottom-right) order used
// by the API to the vertex order of the billboard mesh.
static const int billboard_vertex_index[4] = {0, 2, 1, 3};
bool RE_instance_set_vertex_pos(struct RE_instance *instance, int index, float x, float y, float z)
{
if (!instance)
if (!instance || (unsigned)index >= 4)
return false;
if (instance->type != RE_ITYPE_BILLBOARD)
ERROR("not implemented");
// Hack: This works because SetInstanceVertexPos is only used to scale
// billboards, and SetInstanceScale is not used for billboards.
if (index == 1) {
instance->scale[0] = x;
instance->scale[1] = y / 2.0;
}
return false;
glm_vec3_copy((vec3){x, y, z}, instance->vertex_pos[billboard_vertex_index[index]]);
return true;
}
bool RE_instance_set_vertex_uv(struct RE_instance *instance, int index, float u, float v)
{
if (!instance || (unsigned)index >= 4)
return false;
if (instance->type != RE_ITYPE_BILLBOARD)
ERROR("not implemented");
glm_vec2_copy((vec2){u, v}, instance->vertex_uv[billboard_vertex_index[index]]);
return true;
}
int RE_instance_get_bone_index(struct RE_instance *instance, const char *name)
@@ -629,7 +800,7 @@ bool RE_instance_find_path(struct RE_instance *inst, vec3 start, vec3 goal)
if (!inst || !inst->model->collider)
return false;
mat4 vp_transform;
RE_calc_view_matrix(&inst->plugin->camera, GLM_YUP, vp_transform);
RE_calc_view_matrix(&inst->plugin->camera, NULL, vp_transform);
glm_mat4_mul(inst->plugin->proj_transform, vp_transform, vp_transform);
return collider_find_path(inst->model->collider, start, goal, vp_transform);
}
@@ -651,7 +822,7 @@ bool RE_instance_optimize_path_line(struct RE_instance *inst)
bool RE_instance_calc_path_finder_intersect_eye_vec(struct RE_instance *inst, int mouse_x, int mouse_y, vec3 out)
{
if (!inst || !inst->model->collider)
if (!inst || !inst->model || !inst->model->collider)
return false;
float ndc_x = 2.f * mouse_x / inst->plugin->renderer->viewport_width - 1.f;
@@ -660,7 +831,7 @@ bool RE_instance_calc_path_finder_intersect_eye_vec(struct RE_instance *inst, in
vec4 far = { ndc_x, ndc_y, 1.f, 1.f };
mat4 inv_vp;
RE_calc_view_matrix(&inst->plugin->camera, GLM_YUP, inv_vp);
RE_calc_view_matrix(&inst->plugin->camera, NULL, inv_vp);
glm_mat4_mul(inst->plugin->proj_transform, inv_vp, inv_vp);
glm_mat4_inv(inv_vp, inv_vp);
@@ -677,6 +848,94 @@ bool RE_instance_calc_path_finder_intersect_eye_vec(struct RE_instance *inst, in
return collider_raycast(inst->model->collider, origin, direction, out);
}
bool RE_plugin_transform_pos_to_view_pos(struct RE_plugin *plugin, float x, float y, float z, int *view_x, int *view_y)
{
if (!plugin)
return false;
mat4 vp;
RE_calc_view_matrix(&plugin->camera, NULL, vp);
glm_mat4_mul(plugin->proj_transform, vp, vp);
vec4 pos = { x, y, z, 1.f };
glm_mat4_mulv(vp, pos, pos);
if (pos[3] == 0.f)
return false;
float ndc_x = pos[0] / pos[3];
float ndc_y = pos[1] / pos[3];
*view_x = (int)((ndc_x + 1.f) * 0.5f * plugin->renderer->viewport_width);
*view_y = (int)((1.f - ndc_y) * 0.5f * plugin->renderer->viewport_height);
return true;
}
bool RE_plugin_get_camera_z_vector(struct RE_plugin *plugin, vec3 out)
{
if (!plugin)
return false;
vec3 euler;
if (plugin->camera.override_active) {
euler[0] = glm_rad(plugin->camera.override_pitch);
euler[1] = glm_rad(plugin->camera.override_yaw);
euler[2] = glm_rad(plugin->camera.override_roll);
} else {
euler[0] = glm_rad(plugin->camera.pitch + plugin->camera.quake_pitch);
euler[1] = glm_rad(plugin->camera.yaw + plugin->camera.quake_yaw);
euler[2] = glm_rad(plugin->camera.roll);
}
mat4 rot;
glm_euler_yxz(euler, rot);
glm_mat4_mulv3(rot, GLM_FORWARD, 0.f, out);
return true;
}
void RE_plugin_reset_light_param(struct RE_plugin *plugin)
{
lit_reset(plugin->light_params);
}
bool RE_plugin_set_fog_type(struct RE_plugin *plugin, int type)
{
if (re_plugin_version == RE_REIGN_PLUGIN) {
switch (type) {
case 0: plugin->fog_type = RE_FOG_NONE; return true;
case 1: plugin->fog_type = RE_FOG_LINEAR; return true;
case 2: plugin->fog_type = RE_FOG_LIGHT_SCATTERING; return true;
default: break;
}
} else {
switch (type) {
case 0: plugin->fog_type = RE_FOG_NONE; return true;
case 1: plugin->fog_type = RE_FOG_LIGHT_SCATTERING; return true;
default: break;
}
}
WARNING("unknown fog type %d", type);
return false;
}
int RE_plugin_get_fog_type(struct RE_plugin *plugin)
{
if (re_plugin_version == RE_REIGN_PLUGIN) {
switch (plugin->fog_type) {
case RE_FOG_NONE: return 0;
case RE_FOG_LINEAR: return 1;
case RE_FOG_LIGHT_SCATTERING: return 2;
default: break;
}
} else {
switch (plugin->fog_type) {
case RE_FOG_NONE: return 0;
case RE_FOG_LIGHT_SCATTERING: return 1;
default: break;
}
}
ERROR("[BUG] unexpected fog type %d", plugin->fog_type);
}
void RE_instance_update_local_transform(struct RE_instance *inst)
{
vec3 euler = {
@@ -731,9 +990,9 @@ bool RE_motion_set_frame_range(struct motion *motion, float begin, float end)
return false;
motion->frame_begin = begin;
motion->frame_end = end;
if (motion->instance->type == RE_ITYPE_BILLBOARD) {
if (motion->instance->type == RE_ITYPE_BILLBOARD && re_plugin_version < RE_SEAL_PLUGIN) {
for (int i = begin; i < end; i++) {
if (!RE_renderer_load_billboard_texture(motion->instance->plugin->renderer, i))
if (!RE_renderer_load_billboard_texture_by_no(motion->instance->plugin->renderer, i))
return false;
}
}
@@ -746,9 +1005,9 @@ bool RE_motion_set_loop_frame_range(struct motion *motion, float begin, float en
return false;
motion->loop_frame_begin = begin;
motion->loop_frame_end = end;
if (motion->instance->type == RE_ITYPE_BILLBOARD) {
if (motion->instance->type == RE_ITYPE_BILLBOARD && re_plugin_version < RE_SEAL_PLUGIN) {
for (int i = begin; i < end; i++) {
if (!RE_renderer_load_billboard_texture(motion->instance->plugin->renderer, i))
if (!RE_renderer_load_billboard_texture_by_no(motion->instance->plugin->renderer, i))
return false;
}
}
+525 -101
View File
@@ -39,6 +39,7 @@ enum {
LIGHT_TEXTURE_UNIT,
NORMAL_TEXTURE_UNIT,
SHADOW_TEXTURE_UNIT,
BLEND_TEXTURE_UNIT,
};
enum {
@@ -66,9 +67,11 @@ static GLuint load_shader(const char *vertex_shader_path, const char *fragment_s
snprintf(defines, sizeof(defines),
"#define REIGN_ENGINE %d\n"
"#define TAPIR_ENGINE %d\n"
"#define SEAL_ENGINE %d\n"
"#define ENGINE %d\n",
RE_REIGN_PLUGIN,
RE_TAPIR_PLUGIN,
RE_SEAL_PLUGIN,
re_plugin_version);
GLuint program = glCreateProgram();
GLuint vertex_shader = gfx_load_shader_file(vertex_shader_path, GL_VERTEX_SHADER, defines);
@@ -87,6 +90,8 @@ static GLuint load_shader(const char *vertex_shader_path, const char *fragment_s
glBindAttribLocation(program, VATTR_LIGHT_UV, "vertex_light_uv");
glBindAttribLocation(program, VATTR_COLOR, "vertex_color");
glBindAttribLocation(program, VATTR_TANGENT, "vertex_tangent");
glBindAttribLocation(program, VATTR_BLEND_WEIGHT, "vertex_blend_weight");
glBindAttribLocation(program, VATTR_BLEND_UV, "vertex_blend_uv");
glLinkProgram(program);
@@ -161,24 +166,8 @@ static void destroy_outline_renderer(struct outline_renderer *or)
glDeleteProgram(or->program);
}
static void init_billboard_mesh(struct RE_renderer *r)
static void init_billboard_unused_vertex_attrs(void)
{
static const GLfloat vertices[] = {
// x, y, z, u, v
-1.0, 1.0, 0.0, 0.0, 0.0,
-1.0, -1.0, 0.0, 0.0, 1.0,
1.0, 1.0, 0.0, 1.0, 0.0,
1.0, -1.0, 0.0, 1.0, 1.0,
};
glGenVertexArrays(1, &r->billboard_vao);
glBindVertexArray(r->billboard_vao);
glGenBuffers(1, &r->billboard_attr_buffer);
glBindBuffer(GL_ARRAY_BUFFER, r->billboard_attr_buffer);
glEnableVertexAttribArray(VATTR_POS);
glVertexAttribPointer(VATTR_POS, 3, GL_FLOAT, GL_FALSE, 20, (const void *)0);
glEnableVertexAttribArray(VATTR_UV);
glVertexAttribPointer(VATTR_UV, 2, GL_FLOAT, GL_FALSE, 20, (const void *)12);
glDisableVertexAttribArray(VATTR_LIGHT_UV);
glVertexAttrib2f(VATTR_LIGHT_UV, 0.0, 0.0);
glDisableVertexAttribArray(VATTR_COLOR);
@@ -191,8 +180,39 @@ static void init_billboard_mesh(struct RE_renderer *r)
glVertexAttribI4i(VATTR_BONE_INDEX, 0, 0, 0, 0);
glDisableVertexAttribArray(VATTR_BONE_WEIGHT);
glVertexAttrib4f(VATTR_BONE_WEIGHT, 0.0, 0.0, 0.0, 0.0);
}
static void init_billboard_mesh(struct RE_renderer *r)
{
static const GLfloat vertices[] = {
// x, y, z, u, v
-1.0, 1.0, 0.0, 0.0, 0.0,
-1.0, -1.0, 0.0, 0.0, 1.0,
1.0, 1.0, 0.0, 1.0, 0.0,
1.0, -1.0, 0.0, 1.0, 1.0,
};
glGenBuffers(1, &r->billboard_particle_attr_buffer);
glBindBuffer(GL_ARRAY_BUFFER, r->billboard_particle_attr_buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glGenBuffers(1, &r->billboard_instance_attr_buffer);
glGenVertexArrays(1, &r->billboard_particle_vao);
glBindVertexArray(r->billboard_particle_vao);
glBindBuffer(GL_ARRAY_BUFFER, r->billboard_particle_attr_buffer);
glEnableVertexAttribArray(VATTR_POS);
glVertexAttribPointer(VATTR_POS, 3, GL_FLOAT, GL_FALSE, 20, (const void *)0);
glEnableVertexAttribArray(VATTR_UV);
glVertexAttribPointer(VATTR_UV, 2, GL_FLOAT, GL_FALSE, 20, (const void *)12);
init_billboard_unused_vertex_attrs();
glGenVertexArrays(1, &r->billboard_instance_vao);
glBindVertexArray(r->billboard_instance_vao);
glBindBuffer(GL_ARRAY_BUFFER, r->billboard_instance_attr_buffer);
glEnableVertexAttribArray(VATTR_POS);
glVertexAttribPointer(VATTR_POS, 3, GL_FLOAT, GL_FALSE, 20, (const void *)0);
glEnableVertexAttribArray(VATTR_UV);
glVertexAttribPointer(VATTR_UV, 2, GL_FLOAT, GL_FALSE, 20, (const void *)12);
init_billboard_unused_vertex_attrs();
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
@@ -200,8 +220,10 @@ static void init_billboard_mesh(struct RE_renderer *r)
static void destroy_billboard_mesh(struct RE_renderer *r)
{
glDeleteVertexArrays(1, &r->billboard_vao);
glDeleteBuffers(1, &r->billboard_attr_buffer);
glDeleteVertexArrays(1, &r->billboard_particle_vao);
glDeleteVertexArrays(1, &r->billboard_instance_vao);
glDeleteBuffers(1, &r->billboard_particle_attr_buffer);
glDeleteBuffers(1, &r->billboard_instance_attr_buffer);
}
struct RE_renderer *RE_renderer_new(void)
@@ -220,6 +242,7 @@ struct RE_renderer *RE_renderer_new(void)
glUniformBlockBinding(r->program, bone_transforms, BONE_TRANSFORMS_BINDING);
r->global_ambient = glGetUniformLocation(r->program, "global_ambient");
r->instance_ambient = glGetUniformLocation(r->program, "instance_ambient");
r->diffuse_mod = glGetUniformLocation(r->program, "diffuse_mod");
for (int i = 0; i < NR_DIR_LIGHTS; i++) {
char buf[64];
sprintf(buf, "dir_lights[%d].dir", i);
@@ -230,7 +253,7 @@ struct RE_renderer *RE_renderer_new(void)
r->dir_lights[i].globe_diffuse = glGetUniformLocation(r->program, buf);
}
r->specular_light_dir = glGetUniformLocation(r->program, "specular_light_dir");
r->specular_strength = glGetUniformLocation(r->program, "specular_strength");
r->specular_color = glGetUniformLocation(r->program, "specular_color");
r->specular_shininess = glGetUniformLocation(r->program, "specular_shininess");
r->use_specular_map = glGetUniformLocation(r->program, "use_specular_map");
r->specular_texture = glGetUniformLocation(r->program, "specular_texture");
@@ -253,16 +276,28 @@ struct RE_renderer *RE_renderer_new(void)
r->ls_light_dir = glGetUniformLocation(r->program, "ls_light_dir");
r->ls_light_color = glGetUniformLocation(r->program, "ls_light_color");
r->ls_sun_color = glGetUniformLocation(r->program, "ls_sun_color");
r->hemi_light_dir = glGetUniformLocation(r->program, "hemi_light_dir");
r->hemi_sky_color = glGetUniformLocation(r->program, "hemi_sky_color");
r->hemi_mid_color = glGetUniformLocation(r->program, "hemi_mid_color");
r->hemi_ground_color = glGetUniformLocation(r->program, "hemi_ground_color");
r->tonemap_param = glGetUniformLocation(r->program, "tonemap_param");
r->tonemap_param2 = glGetUniformLocation(r->program, "tonemap_param2");
r->nolighting = glGetUniformLocation(r->program, "nolighting");
r->grayscale_rate = glGetUniformLocation(r->program, "grayscale_rate");
r->alpha_mode = glGetUniformLocation(r->program, "alpha_mode");
r->alpha_texture = glGetUniformLocation(r->program, "alpha_texture");
r->uv_scroll = glGetUniformLocation(r->program, "uv_scroll");
r->uv_tiling = glGetUniformLocation(r->program, "uv_tiling");
r->blend_tex = glGetUniformLocation(r->program, "blend_tex");
r->use_blend_texture = glGetUniformLocation(r->program, "use_blend_texture");
glGenRenderbuffers(1, &r->depth_buffer);
init_shadow_renderer(&r->shadow);
init_outline_renderer(&r->outline);
init_billboard_mesh(r);
r->billboard_textures = ht_create(256);
r->billboard_textures_by_no = ht_create(256);
r->billboard_textures_by_path = ht_create(256);
r->last_frame_timestamp = SDL_GetTicks();
return r;
}
@@ -276,15 +311,8 @@ void RE_renderer_set_viewport_size(struct RE_renderer *r, int width, int height)
glBindRenderbuffer(GL_RENDERBUFFER, 0);
}
bool RE_renderer_load_billboard_texture(struct RE_renderer *r, int cg_no)
static struct billboard_texture *create_billboard_texture(struct cg *cg)
{
if (ht_get_int(r->billboard_textures, cg_no, NULL))
return true;
struct cg *cg = asset_cg_load(cg_no);
if (!cg)
return false;
struct billboard_texture *bt = xcalloc(1, sizeof(struct billboard_texture));
glGenTextures(1, &bt->texture);
glBindTexture(GL_TEXTURE_2D, bt->texture);
@@ -296,12 +324,45 @@ bool RE_renderer_load_billboard_texture(struct RE_renderer *r, int cg_no)
glGenerateMipmap(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, 0);
bt->has_alpha = cg->metrics.has_alpha;
return bt;
}
bool RE_renderer_load_billboard_texture_by_no(struct RE_renderer *r, int cg_no)
{
if (ht_get_int(r->billboard_textures_by_no, cg_no, NULL))
return true;
struct cg *cg = asset_cg_load(cg_no);
if (!cg)
return false;
ht_put_int(r->billboard_textures_by_no, cg_no, create_billboard_texture(cg));
cg_free(cg);
ht_put_int(r->billboard_textures, cg_no, bt);
return true;
}
struct billboard_texture *RE_renderer_load_billboard_texture_by_path(struct RE_renderer *r, struct archive *aar, const char *path)
{
struct billboard_texture *bt = ht_get(r->billboard_textures_by_path, path, NULL);
if (bt)
return bt;
struct archive_data *dfile = archive_get_by_name(aar, path);
if (!dfile)
return NULL;
struct cg *cg = cg_load_data(dfile);
archive_free_data(dfile);
if (!cg) {
WARNING("cg_load_data failed: %s", path);
return NULL;
}
bt = create_billboard_texture(cg);
cg_free(cg);
ht_put(r->billboard_textures_by_path, path, bt);
return bt;
}
static void free_billboard_texture(void *value)
{
struct billboard_texture *bt = value;
@@ -313,8 +374,10 @@ void RE_renderer_free(struct RE_renderer *r)
{
glDeleteProgram(r->program);
glDeleteRenderbuffers(1, &r->depth_buffer);
ht_foreach_value(r->billboard_textures, free_billboard_texture);
ht_free_int(r->billboard_textures);
ht_foreach_value(r->billboard_textures_by_no, free_billboard_texture);
ht_free_int(r->billboard_textures_by_no);
ht_foreach_value(r->billboard_textures_by_path, free_billboard_texture);
ht_free(r->billboard_textures_by_path);
destroy_billboard_mesh(r);
destroy_shadow_renderer(&r->shadow);
destroy_outline_renderer(&r->outline);
@@ -324,40 +387,74 @@ void RE_renderer_free(struct RE_renderer *r)
void RE_calc_view_matrix(struct RE_camera *camera, vec3 up, mat4 out)
{
vec3 front = { 0.0, 0.0, -1.0 };
vec3 euler = {
glm_rad(camera->pitch + camera->quake_pitch),
glm_rad(camera->yaw + camera->quake_yaw),
glm_rad(camera->roll)
};
vec3 euler;
float *pos;
if (camera->override_active) {
euler[0] = glm_rad(camera->override_pitch);
euler[1] = glm_rad(camera->override_yaw);
euler[2] = glm_rad(camera->override_roll);
pos = camera->override_pos;
} else {
euler[0] = glm_rad(camera->pitch + camera->quake_pitch);
euler[1] = glm_rad(camera->yaw + camera->quake_yaw);
euler[2] = glm_rad(camera->roll);
pos = camera->pos;
}
mat4 rot;
glm_euler(euler, rot);
glm_euler_yxz(euler, rot);
glm_mat4_mulv3(rot, front, 0.0, front);
glm_look(camera->pos, front, up, out);
vec3 up_v;
if (up) {
glm_vec3_copy(up, up_v); // Use the provided up vector.
} else {
// Derive the camera's up by rotating world-Y with the same orientation.
glm_mat4_mulv3(rot, GLM_YUP, 0.0, up_v);
}
glm_look(pos, front, up_v, out);
}
static bool should_draw_shadow(struct mesh *mesh, struct material *material)
{
return !(mesh->flags & (MESH_ALPHA | MESH_BOTH | MESH_SPRITE))
return !(mesh->flags & (MESH_ALPHA | MESH_BOTH | MESH_SPRITE | MESH_NO_DRAWSHADOW))
&& !(material->flags & (MATERIAL_ALPHA | MATERIAL_SPRITE));
}
static bool lighting_disabled(struct RE_plugin *plugin)
{
return plugin->draw_options[RE_DRAW_OPTION_LIGHTING] <= 0;
}
static bool is_nolighting(struct RE_plugin *plugin, struct mesh *mesh)
{
return lighting_disabled(plugin) || (mesh->flags & MESH_NOLIGHTING);
}
static void render_model(struct RE_instance *inst, struct RE_renderer *r, enum draw_phase phase)
{
struct model *model = inst->model;
if (!inst->model)
return;
bool all_transparent = inst->alpha < 1.0f;
bool all_opaque = !model->has_transparent_mesh && inst->alpha >= 1.0f;
// NOTE: next_motion's .mpr is not sampled.
const struct mpr *mpr = (inst->motion && inst->motion->mot) ? inst->motion->mot->mpr : NULL;
float frame = inst->motion ? inst->motion->current_frame : 0.f;
struct mpr_modulation obj_mod;
mpr_evaluate_object(mpr, frame, inst, &obj_mod);
bool all_transparent = obj_mod.alpha < 1.0f;
bool all_opaque = !model->has_transparent_mesh && obj_mod.alpha >= 1.0f
&& !(mpr && mpr->has_mesh_alpha);
if ((phase == DRAW_OPAQUE && all_transparent) || (phase == DRAW_TRANSPARENT && all_opaque))
return;
int mat_tex_index[model->nr_materials];
mpr_build_mat_tex_index(model, inst, mpr, frame, mat_tex_index);
if (inst->local_transform_needs_update)
RE_instance_update_local_transform(inst);
glUniformMatrix4fv(r->local_transform, 1, GL_FALSE, inst->local_transform[0]);
glUniformMatrix3fv(r->normal_transform, 1, GL_FALSE, inst->normal_transform[0]);
glUniform1f(r->alpha_mod, inst->alpha);
glUniform3fv(r->instance_ambient, 1, inst->ambient);
bool draw_shadow = inst->draw_shadow && inst->plugin->shadow_mode;
if (draw_shadow) {
@@ -371,23 +468,44 @@ static void render_model(struct RE_instance *inst, struct RE_renderer *r, enum d
if (mesh->hidden)
continue;
struct material *material = &model->materials[mesh->material];
bool is_transparent = mesh->is_transparent || inst->alpha < 1.0f;
const struct mpr_track_set *mt = mpr ? mpr->mesh_tracks[i] : NULL;
struct mpr_modulation mesh_mod;
mpr_evaluate_mesh(mt, frame, &obj_mod, &mesh_mod);
if (mesh_mod.alpha <= 0.0f)
continue;
bool is_transparent = mesh->is_transparent || mesh_mod.alpha < 1.0f;
if (phase != (is_transparent ? DRAW_TRANSPARENT : DRAW_OPAQUE))
continue;
glUniform1f(r->alpha_mod, mesh_mod.alpha);
glUniform3fv(r->instance_ambient, 1, mesh_mod.ambient);
glUniform3fv(r->diffuse_mod, 1, mesh_mod.diffuse);
if (mesh->flags & MESH_BLEND_ADDITIVE) {
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE, GL_ZERO, GL_ONE);
} else {
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE);
}
glUniform1i(r->fog_type, (inst->plugin->fog_mode && !(mesh->flags & MESH_NOLIGHTING))
? inst->plugin->fog_type : 0);
bool nolighting = is_nolighting(inst->plugin, mesh);
int fog_type = inst->plugin->fog_type;
if (!inst->plugin->fog_mode || (re_plugin_version == RE_REIGN_PLUGIN && nolighting)) {
fog_type = RE_FOG_NONE;
}
glUniform1i(r->fog_type, fog_type);
glUniform1i(r->nolighting, nolighting);
GLboolean use_specular_map = GL_FALSE;
if (inst->plugin->specular_mode && !(mesh->flags & MESH_NOLIGHTING)) {
glUniform1f(r->specular_strength, material->specular_strength);
glUniform1f(r->specular_shininess, material->specular_shininess);
float shininess = (mesh->flags & MESH_HAS_SPECULAR_POWER) ? mesh->specular_power : material->specular_shininess;
if (inst->plugin->specular_mode && !nolighting && shininess > 0.0f) {
if (mesh->flags & MESH_HAS_SPECULAR_COLOR) {
glUniform3fv(r->specular_color, 1, mesh->specular_color);
} else {
glUniform3f(r->specular_color, material->specular_strength, material->specular_strength, material->specular_strength);
}
glUniform1f(r->specular_shininess, shininess);
if (material->specular_map) {
glActiveTexture(GL_TEXTURE0 + SPECULAR_TEXTURE_UNIT);
glBindTexture(GL_TEXTURE_2D, material->specular_map);
@@ -395,8 +513,8 @@ static void render_model(struct RE_instance *inst, struct RE_renderer *r, enum d
use_specular_map = GL_TRUE;
}
} else {
glUniform1f(r->specular_strength, 0.0);
glUniform1f(r->specular_shininess, 0.0);
glUniform3f(r->specular_color, 0.0f, 0.0f, 0.0f);
glUniform1f(r->specular_shininess, 0.0f);
}
glUniform1i(r->use_specular_map, use_specular_map);
@@ -404,8 +522,8 @@ static void render_model(struct RE_instance *inst, struct RE_renderer *r, enum d
glUniform3fv(r->rim_color, 1, material->rim_color);
glActiveTexture(GL_TEXTURE0 + COLOR_TEXTURE_UNIT);
int animation_index = inst->texture_animation_index < material->nr_color_maps
? inst->texture_animation_index : 0;
int tex_index = mat_tex_index[mesh->material];
int animation_index = tex_index < material->nr_color_maps ? tex_index : 0;
glBindTexture(GL_TEXTURE_2D, material->color_maps[animation_index]);
glUniform1i(r->texture, COLOR_TEXTURE_UNIT);
@@ -416,7 +534,7 @@ static void render_model(struct RE_instance *inst, struct RE_renderer *r, enum d
if (mesh->flags & MESH_ENVMAP) {
glUniform1i(r->diffuse_type, DIFFUSE_ENV_MAP);
} else if (mesh->flags & MESH_NOLIGHTING) {
} else if (nolighting) {
glUniform1i(r->diffuse_type, DIFFUSE_EMISSIVE);
} else if (material->light_map && mesh->flags & MESH_HAS_LIGHT_UV && inst->plugin->light_map_mode) {
glUniform1i(r->diffuse_type, DIFFUSE_LIGHT_MAP);
@@ -437,28 +555,44 @@ static void render_model(struct RE_instance *inst, struct RE_renderer *r, enum d
}
if (material->alpha_map) {
glUniform1i(r->alpha_mode, mesh->is_transparent ? ALPHA_MAP_BLEND : ALPHA_MAP_TEST);
glUniform1i(r->alpha_mode, is_transparent ? ALPHA_MAP_BLEND : ALPHA_MAP_TEST);
glActiveTexture(GL_TEXTURE0 + ALPHA_TEXTURE_UNIT);
glBindTexture(GL_TEXTURE_2D, material->alpha_map);
glUniform1i(r->alpha_texture, ALPHA_TEXTURE_UNIT);
} else {
glUniform1i(r->alpha_mode, mesh->is_transparent ? ALPHA_BLEND : ALPHA_TEST);
glUniform1i(r->alpha_mode, is_transparent ? ALPHA_BLEND : ALPHA_TEST);
}
if (material->blend_texture) {
glUniform1i(r->use_blend_texture, GL_TRUE);
glActiveTexture(GL_TEXTURE0 + BLEND_TEXTURE_UNIT);
glBindTexture(GL_TEXTURE_2D, material->blend_texture);
glUniform1i(r->blend_tex, BLEND_TEXTURE_UNIT);
} else {
glUniform1i(r->use_blend_texture, GL_FALSE);
}
vec2 uv_scroll;
glm_vec2_scale(mesh->uv_scroll, r->last_frame_timestamp / 1000.f, uv_scroll);
glUniform2fv(r->uv_scroll, 1, uv_scroll);
glUniform4f(r->uv_tiling,
material->uv_tiling[0], material->uv_tiling[1],
material->blend_uv_tiling[0], material->blend_uv_tiling[1]);
glBindVertexArray(mesh->vao);
if (mesh->flags & MESH_BOTH)
glDisable(GL_CULL_FACE);
if (mesh->flags & MESH_NO_ZWRITE)
glDepthMask(GL_FALSE);
if (mesh->nr_indices)
glDrawElements(GL_TRIANGLES, mesh->nr_indices, GL_UNSIGNED_SHORT, NULL);
else
glDrawArrays(GL_TRIANGLES, 0, mesh->nr_vertices);
if (mesh->flags & MESH_NO_ZWRITE)
glDepthMask(GL_TRUE);
if (mesh->flags & MESH_BOTH)
glEnable(GL_CULL_FACE);
glBindVertexArray(0);
@@ -494,12 +628,41 @@ static void render_skinned_model(struct RE_instance *inst, struct RE_renderer *r
render_static_model(inst->shadow_volume_instance, r, phase);
}
static void reset_draw_uniforms(struct RE_renderer *r, struct RE_plugin *plugin)
{
bool nolighting = lighting_disabled(plugin);
glUniform1f(r->alpha_mod, 1.0f);
glUniform3f(r->diffuse_mod, 1.0f, 1.0f, 1.0f);
glUniform2f(r->uv_scroll, 0.0f, 0.0f);
glUniform4f(r->uv_tiling, 1.0f, 1.0f, 1.0f, 1.0f);
glUniform1i(r->has_bones, GL_FALSE);
glUniform3f(r->specular_color, 0.0f, 0.0f, 0.0f);
glUniform1f(r->specular_shininess, 0.0f);
glUniform1i(r->use_specular_map, GL_FALSE);
glUniform1f(r->rim_exponent, 0.0f);
glUniform3f(r->rim_color, 0.0f, 0.0f, 0.0f);
glUniform1i(r->use_normal_map, GL_FALSE);
glUniform1i(r->use_blend_texture, GL_FALSE);
glUniform1f(r->shadow_darkness, 0.0f);
glUniform1i(r->alpha_mode, ALPHA_BLEND);
glUniform1i(r->fog_type, 0);
glUniform1i(r->nolighting, nolighting);
glUniform1i(r->diffuse_type, nolighting ? DIFFUSE_EMISSIVE : DIFFUSE_NORMAL);
}
static void render_billboard(struct RE_instance *inst, struct RE_renderer *r, mat4 view_mat, enum draw_phase phase)
{
if (!inst->draw)
return;
int cg_no = inst->motion->current_frame;
struct billboard_texture *bt = ht_get_int(r->billboard_textures, cg_no, NULL);
int frame = inst->motion->current_frame;
struct billboard_texture *bt;
if (inst->nr_billboard_frames > 0) {
if (frame < 0 || frame >= inst->nr_billboard_frames)
return;
bt = inst->billboard_frames[frame];
} else {
bt = ht_get_int(r->billboard_textures_by_no, frame, NULL);
}
if (!bt)
return;
bool is_transparent = bt->has_alpha || inst->alpha < 1.0f;
@@ -512,26 +675,16 @@ static void render_billboard(struct RE_instance *inst, struct RE_renderer *r, ma
glm_mat4_pick3t(view_mat, rot);
glm_mat4_ins3(rot, local_transform);
glm_scale(local_transform, inst->scale);
// Billboard instances are bottomed at y=0.
glm_translate_y(local_transform, 1.0);
mat3 normal_transform;
// This should be safe because billboards do not have non-uniform scaling.
glm_mat4_pick3(local_transform, normal_transform);
glUniform3fv(r->instance_ambient, 1, inst->ambient);
glUniformMatrix4fv(r->local_transform, 1, GL_FALSE, local_transform[0]);
glUniformMatrix3fv(r->normal_transform, 1, GL_FALSE, normal_transform[0]);
glUniform1i(r->has_bones, GL_FALSE);
reset_draw_uniforms(r, inst->plugin);
glUniform1f(r->alpha_mod, inst->alpha);
glUniform1f(r->specular_strength, 0.0);
glUniform1f(r->specular_shininess, 0.0);
glUniform1i(r->use_specular_map, GL_FALSE);
glUniform1f(r->rim_exponent, 0.0);
glUniform1i(r->diffuse_type, DIFFUSE_NORMAL);
glUniform1i(r->use_normal_map, GL_FALSE);
glUniform1f(r->shadow_darkness, 0.0f);
glUniform1i(r->alpha_mode, ALPHA_BLEND);
glUniform1i(r->fog_type, inst->plugin->fog_mode ? inst->plugin->fog_type : 0);
switch (inst->draw_type) {
case RE_DRAW_TYPE_NORMAL:
@@ -546,7 +699,15 @@ static void render_billboard(struct RE_instance *inst, struct RE_renderer *r, ma
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, bt->texture);
glUniform1i(r->texture, 0);
glBindVertexArray(r->billboard_vao);
GLfloat vertices[4][5];
for (int i = 0; i < 4; i++) {
glm_vec3_copy(inst->vertex_pos[i], vertices[i]);
glm_vec2_copy(inst->vertex_uv[i], vertices[i] + 3);
}
glBindVertexArray(r->billboard_instance_vao);
glBindBuffer(GL_ARRAY_BUFFER, r->billboard_instance_attr_buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STREAM_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
@@ -566,7 +727,7 @@ static void render_billboard_particles(struct RE_renderer *r, struct RE_instance
glActiveTexture(GL_TEXTURE0);
glUniform1i(r->texture, 0);
glBindVertexArray(r->billboard_vao);
glBindVertexArray(r->billboard_particle_vao);
for (int index = 0; index < pae_obj->nr_particles; index++) {
struct particle_instance *pi = &po->instances[index];
@@ -603,7 +764,8 @@ static void render_polygon_particles(struct RE_renderer *r, struct RE_instance *
if (!model)
return;
glUniform1i(r->diffuse_type, DIFFUSE_NORMAL);
glUniform1i(r->diffuse_type,
lighting_disabled(inst->plugin) ? DIFFUSE_EMISSIVE : DIFFUSE_NORMAL);
glUniform1i(r->fog_type, inst->plugin->fog_mode ? inst->plugin->fog_type : 0);
for (int index = 0; index < pae_obj->nr_particles; index++) {
@@ -622,6 +784,9 @@ static void render_polygon_particles(struct RE_renderer *r, struct RE_instance *
for (int i = 0; i < model->nr_meshes; i++) {
struct mesh *mesh = &model->meshes[i];
struct material *material = &model->materials[mesh->material];
glUniform4f(r->uv_tiling,
material->uv_tiling[0], material->uv_tiling[1],
material->blend_uv_tiling[0], material->blend_uv_tiling[1]);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, material->color_maps[0]);
@@ -636,6 +801,204 @@ static void render_polygon_particles(struct RE_renderer *r, struct RE_instance *
}
}
static void render_s3de_billboard_particles(struct RE_renderer *r, struct RE_instance *inst,
struct s3de_object *obj, struct s3de_object_state *st,
mat3 camera_rot, float frame)
{
if (!obj->texture || obj->particle_count == 0)
return;
struct billboard_texture *bt = ht_get(
inst->s3de_effect->s3de->textures, obj->texture, NULL);
if (!bt)
return;
glUniform1i(r->diffuse_type, DIFFUSE_EMISSIVE);
switch (obj->blend_type) {
case S3DE_BLEND_NORMAL:
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE);
break;
case S3DE_BLEND_ADDITIVE:
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE, GL_ZERO, GL_ONE);
break;
}
glActiveTexture(GL_TEXTURE0);
glUniform1i(r->texture, 0);
glBindVertexArray(r->billboard_particle_vao);
glDisable(GL_CULL_FACE);
glBindTexture(GL_TEXTURE_2D, bt->texture);
for (int i = 0; i < obj->particle_count; i++) {
struct s3de_particle *p = &st->particles[i];
float alpha;
if (!s3de_particle_alpha(st, p, frame, &alpha))
continue;
mat4 world;
if (!s3de_billboard_world_transform(inst, obj, st, p, frame, camera_rot, world))
continue;
glUniform1f(r->alpha_mod, alpha);
glUniformMatrix4fv(r->local_transform, 1, GL_FALSE, world[0]);
glUniformMatrix3fv(r->normal_transform, 1, GL_FALSE, world[0]);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
glEnable(GL_CULL_FACE);
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);
}
static void render_s3de_polygon_particles(struct RE_renderer *r, struct RE_instance *inst,
struct s3de_object *obj, struct s3de_object_state *st,
mat3 camera_rot, vec3 camera_pos, float frame,
enum draw_phase phase)
{
struct model *model = obj->model;
if (!model || obj->particle_count == 0)
return;
if (phase == DRAW_OPAQUE && st->emitter_alpha < 1.0f)
return;
glUniform1i(r->diffuse_type,
lighting_disabled(inst->plugin) ? DIFFUSE_EMISSIVE : DIFFUSE_NORMAL);
// The .3de blend_type is ignored for polygon objects. Per-mesh additive
// blend modes are not yet handled.
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE);
for (int i = 0; i < obj->particle_count; i++) {
struct s3de_particle *p = &st->particles[i];
float alpha;
if (!s3de_particle_alpha(st, p, frame, &alpha))
continue;
if (phase == DRAW_OPAQUE && alpha < 1.0f)
continue;
mat4 world;
if (!s3de_mesh_world_transform(inst, obj, st, p, frame, camera_rot, camera_pos, world))
continue;
glUniform1f(r->alpha_mod, alpha);
glUniformMatrix4fv(r->local_transform, 1, GL_FALSE, world[0]);
glUniformMatrix3fv(r->normal_transform, 1, GL_FALSE, world[0]);
for (int j = 0; j < model->nr_meshes; j++) {
struct mesh *mesh = &model->meshes[j];
bool is_transparent = mesh->is_transparent || alpha < 1.0f;
if (phase != (is_transparent ? DRAW_TRANSPARENT : DRAW_OPAQUE))
continue;
struct material *material = &model->materials[mesh->material];
glUniform4f(r->uv_tiling,
material->uv_tiling[0], material->uv_tiling[1],
material->blend_uv_tiling[0], material->blend_uv_tiling[1]);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, material->color_maps[0]);
glUniform1i(r->texture, 0);
glBindVertexArray(mesh->vao);
if (mesh->flags & MESH_BOTH)
glDisable(GL_CULL_FACE);
glDrawArrays(GL_TRIANGLES, 0, mesh->nr_vertices);
if (mesh->flags & MESH_BOTH)
glEnable(GL_CULL_FACE);
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);
}
}
}
// Sort entry for per-object ordering.
struct s3de_obj_sort_key {
float key;
int idx;
};
static int cmp_s3de_obj(const void *lhs, const void *rhs)
{
const struct s3de_obj_sort_key *l = lhs;
const struct s3de_obj_sort_key *r = rhs;
if (l->key != r->key)
return (l->key > r->key) - (l->key < r->key);
// Fall back to declaration order to keep the sort stable.
return (l->idx > r->idx) - (l->idx < r->idx);
}
static void render_s3de_effect(struct RE_instance *inst, struct RE_renderer *r, enum draw_phase phase)
{
if (!inst->s3de_effect || !inst->draw)
return;
if (inst->local_transform_needs_update)
RE_instance_update_local_transform(inst);
glUniform3fv(r->instance_ambient, 1, inst->ambient);
reset_draw_uniforms(r, inst->plugin);
if (phase == DRAW_TRANSPARENT)
glDepthMask(GL_FALSE);
mat4 view_mat;
RE_calc_view_matrix(&inst->plugin->camera, NULL, view_mat);
mat3 camera_rot;
glm_mat4_pick3t(view_mat, camera_rot);
struct s3de *s = inst->s3de_effect->s3de;
// Sort objects by their emitter's world-space distance to the camera.
// The transparent phase draws back-to-front, the opaque phase draws
// front-to-back. Particles inside one object are not sorted; they stay
// in spawn order.
float *camera_pos = inst->plugin->camera.override_active
? inst->plugin->camera.override_pos : inst->plugin->camera.pos;
float key_sign = phase == DRAW_OPAQUE ? 1.0f : -1.0f;
struct s3de_obj_sort_key *keys = xmalloc(s->nr_objects * sizeof(*keys));
for (int i = 0; i < s->nr_objects; i++) {
keys[i].idx = i;
vec3 emitter_world;
glm_mat4_mulv3(inst->local_transform,
inst->s3de_effect->objects[i].emitter_pos, 1.0f,
emitter_world);
vec3 d;
glm_vec3_sub(emitter_world, camera_pos, d);
keys[i].key = key_sign * glm_vec3_norm2(d);
}
qsort(keys, s->nr_objects, sizeof(*keys), cmp_s3de_obj);
float frame = inst->motion->current_frame;
for (int k = 0; k < s->nr_objects; k++) {
int i = keys[k].idx;
struct s3de_object *obj = &s->objects[i];
struct s3de_object_state *st = &inst->s3de_effect->objects[i];
vec3 ambient;
glm_vec3_add(inst->ambient, st->additive_color, ambient);
glUniform3fv(r->instance_ambient, 1, ambient);
glUniform3fv(r->diffuse_mod, 1, st->multiply_color);
switch (obj->type) {
case S3DE_OBJ_BILLBOARD:
// Billboards are transparent-phase only.
if (phase == DRAW_TRANSPARENT)
render_s3de_billboard_particles(r, inst, obj, st, camera_rot, frame);
break;
case S3DE_OBJ_POLYGON:
render_s3de_polygon_particles(r, inst, obj, st, camera_rot, camera_pos, frame, phase);
break;
case S3DE_OBJ_CAMERA:
break;
}
}
free(keys);
if (phase == DRAW_TRANSPARENT)
glDepthMask(GL_TRUE);
}
static void render_particle_effect(struct RE_instance *inst, struct RE_renderer *r, enum draw_phase phase)
{
// NOTE: inst->draw flag has no effect for particle effects.
@@ -643,15 +1006,7 @@ static void render_particle_effect(struct RE_instance *inst, struct RE_renderer
return;
glUniform3fv(r->instance_ambient, 1, inst->ambient);
glUniform1i(r->has_bones, GL_FALSE);
glUniform1f(r->specular_strength, 0.0);
glUniform1f(r->specular_shininess, 0.0);
glUniform1i(r->use_specular_map, GL_FALSE);
glUniform1f(r->rim_exponent, 0.0);
glUniform1i(r->use_normal_map, GL_FALSE);
glUniform1f(r->shadow_darkness, 0.0f);
glUniform1i(r->alpha_mode, ALPHA_BLEND);
reset_draw_uniforms(r, inst->plugin);
glDepthMask(GL_FALSE);
@@ -684,6 +1039,8 @@ static void render_particle_effect(struct RE_instance *inst, struct RE_renderer
static void render_instance(struct RE_instance *inst, struct RE_renderer *r, mat4 view_mat, enum draw_phase phase)
{
glUniform1f(r->grayscale_rate, inst->grayscale_rate);
switch (inst->type) {
case RE_ITYPE_STATIC:
render_static_model(inst, r, phase);
@@ -695,7 +1052,10 @@ static void render_instance(struct RE_instance *inst, struct RE_renderer *r, mat
render_billboard(inst, r, view_mat, phase);
break;
case RE_ITYPE_PARTICLE_EFFECT:
render_particle_effect(inst, r, phase);
if (inst->s3de_effect)
render_s3de_effect(inst, r, phase);
else
render_particle_effect(inst, r, phase);
break;
default:
break;
@@ -800,8 +1160,7 @@ static void render_shadow_map(struct RE_plugin *plugin, mat4 light_space_transfo
static void render_outlines(struct RE_plugin *plugin, mat4 view_transform)
{
enum RE_draw_edge_mode mode = plugin->draw_options[RE_DRAW_OPTION_EDGE];
if (mode == RE_DRAW_EDGE_NONE)
if (plugin->draw_options[RE_DRAW_OPTION_EDGE] == 0)
return;
struct outline_renderer *or = &plugin->renderer->outline;
if (!or->program)
@@ -810,12 +1169,21 @@ static void render_outlines(struct RE_plugin *plugin, mat4 view_transform)
glUniformMatrix4fv(or->view_transform, 1, GL_FALSE, view_transform[0]);
glUniformMatrix4fv(or->proj_transform, 1, GL_FALSE, plugin->proj_transform[0]);
// SealEngine ignores the per-mesh edge parameters from .opr and uses
// a single plugin-wide edge length and color.
bool seal_edge = re_plugin_version >= RE_SEAL_PLUGIN;
if (seal_edge) {
glUniform3fv(or->outline_color, 1, plugin->edge_color);
float thickness = plugin->edge_length * (1.f - plugin->edge_reduction_rate);
glUniform1f(or->outline_thickness, thickness);
}
glCullFace(GL_FRONT);
for (int i = 0; i < plugin->nr_instances; i++) {
struct RE_instance *inst = plugin->instances[i];
if (!inst || !inst->draw)
continue;
if (mode == RE_DRAW_EDGE_CHARACTERS_ONLY && inst->type != RE_ITYPE_SKINNED)
if (!inst->draw_edge)
continue;
struct model *model = inst->model;
if (inst->model && model->nr_bones > 0) {
@@ -830,8 +1198,10 @@ static void render_outlines(struct RE_plugin *plugin, mat4 view_transform)
struct mesh *mesh = &model->meshes[j];
if (mesh->flags & MESH_NO_EDGE)
continue;
glUniform3fv(or->outline_color, 1, mesh->outline_color);
glUniform1f(or->outline_thickness, mesh->outline_thickness);
if (!seal_edge) {
glUniform3fv(or->outline_color, 1, mesh->outline_color);
glUniform1f(or->outline_thickness, mesh->outline_thickness);
}
glBindVertexArray(mesh->vao);
glDrawArrays(GL_TRIANGLES, 0, mesh->nr_vertices);
}
@@ -850,10 +1220,58 @@ static void render_back_cg(struct texture *dst, struct RE_back_cg *bcg, struct R
gfx_copy_stretch_blend_amap_alpha(dst, bcg->x, bcg->y, sw * bcg->mag, sh * bcg->mag, &bcg->texture, 0, 0, sw, sh, bcg->blend_rate * 255);
}
static void setup_light_scattering(struct RE_plugin *plugin)
{
struct RE_renderer *r = plugin->renderer;
if (re_plugin_version >= RE_SEAL_PLUGIN) {
const float *lp = plugin->light_params;
glUniform4f(r->ls_params, lp[SEAL_LP_LS_BETA_R], lp[SEAL_LP_LS_BETA_M],
lp[SEAL_LP_LS_G], lp[SEAL_LP_LS_DISTANCE]);
glUniform3f(r->ls_light_dir, lp[SEAL_LP_LS_LIGHT_VEC],
lp[SEAL_LP_LS_LIGHT_VEC + 1], -lp[SEAL_LP_LS_LIGHT_VEC + 2]);
glUniform3fv(r->ls_light_color, 1, &lp[SEAL_LP_LS_LIGHT_COLOR]);
glUniform3fv(r->ls_sun_color, 1, &lp[SEAL_LP_LS_SUN_COLOR]);
} else {
glUniform4f(r->ls_params, plugin->ls_beta_r, plugin->ls_beta_m, plugin->ls_g, plugin->ls_distance);
glUniform3fv(r->ls_light_dir, 1, plugin->ls_light_dir);
glUniform3fv(r->ls_light_color, 1, plugin->ls_light_color);
glUniform3fv(r->ls_sun_color, 1, plugin->ls_sun_color);
}
}
static void setup_hemisphere_light(struct RE_plugin *plugin)
{
struct RE_renderer *r = plugin->renderer;
const float *lp = plugin->light_params;
vec3 hemi_dir = {
-lp[SEAL_LP_HEMI_VEC], -lp[SEAL_LP_HEMI_VEC + 1], lp[SEAL_LP_HEMI_VEC + 2]
};
glm_vec3_normalize(hemi_dir);
glUniform3fv(r->hemi_light_dir, 1, hemi_dir);
glUniform3fv(r->hemi_sky_color, 1, &lp[SEAL_LP_HEMI_SKY_COLOR]);
glUniform3fv(r->hemi_mid_color, 1, &lp[SEAL_LP_HEMI_MID_COLOR]);
glUniform3fv(r->hemi_ground_color, 1, &lp[SEAL_LP_HEMI_GROUND_COLOR]);
// SealEngine's specular uses the hemisphere light vector. hemi_dir points
// toward the light, but the shader's specular formula expects the light
// propagation direction.
vec3 spec_dir;
glm_vec3_negate_to(hemi_dir, spec_dir);
glUniform3fv(r->specular_light_dir, 1, spec_dir);
}
static void setup_tone_mapping(struct RE_plugin *plugin)
{
struct RE_renderer *r = plugin->renderer;
const float *lp = plugin->light_params;
glUniform4fv(r->tonemap_param, 1, &lp[SEAL_LP_TONEMAP_EXPOSURE_BIAS]);
glUniform4fv(r->tonemap_param2, 1, &lp[SEAL_LP_TONEMAP_C]);
}
static void setup_lights(struct RE_plugin *plugin)
{
struct RE_renderer *r = plugin->renderer;
glUniform3fv(r->camera_pos, 1, plugin->camera.pos);
glUniform3fv(r->camera_pos, 1, plugin->camera.override_active
? plugin->camera.override_pos : plugin->camera.pos);
glUniform3fv(r->global_ambient, 1, plugin->global_ambient);
int light_index = 0;
for (int i = 0; i < plugin->nr_instances; i++) {
@@ -884,6 +1302,9 @@ static void setup_lights(struct RE_plugin *plugin)
glUniform3fv(r->dir_lights[light_index].diffuse, 1, zero);
glUniform3fv(r->dir_lights[light_index].globe_diffuse, 1, zero);
}
if (re_plugin_version >= RE_SEAL_PLUGIN)
setup_hemisphere_light(plugin);
}
static int cmp_instances_by_z(const void *lhs, const void *rhs)
@@ -921,6 +1342,16 @@ static struct RE_instance **sort_instances(struct RE_plugin *plugin, mat4 view_t
return instances;
}
void RE_update_model(struct RE_plugin *plugin)
{
struct RE_renderer *r = plugin->renderer;
if (!r || plugin->suspended)
return;
uint32_t timestamp = SDL_GetTicks();
RE_build_model(plugin, timestamp - r->last_frame_timestamp);
r->last_frame_timestamp = timestamp;
}
void RE_render(struct sact_sprite *sp)
{
struct RE_plugin *plugin = (struct RE_plugin *)sp->plugin;
@@ -928,12 +1359,6 @@ void RE_render(struct sact_sprite *sp)
if (!r || plugin->suspended)
return;
if (re_plugin_version == RE_TAPIR_PLUGIN) {
uint32_t timestamp = SDL_GetTicks();
RE_build_model(plugin, timestamp - r->last_frame_timestamp);
r->last_frame_timestamp = timestamp;
}
sprite_dirty(sp);
struct texture *texture = sprite_get_texture(sp);
@@ -961,7 +1386,7 @@ void RE_render(struct sact_sprite *sp)
glEnable(GL_CULL_FACE);
mat4 view_transform;
RE_calc_view_matrix(&plugin->camera, GLM_YUP, view_transform);
RE_calc_view_matrix(&plugin->camera, NULL, view_transform);
// Tweak the projection transform so that the rendering result is vertically
// flipped. If we render the scene normally, the resulting image will be
@@ -987,10 +1412,7 @@ void RE_render(struct sact_sprite *sp)
glUniform3fv(r->fog_color, 1, plugin->fog_color);
break;
case RE_FOG_LIGHT_SCATTERING:
glUniform4f(r->ls_params, plugin->ls_beta_r, plugin->ls_beta_m, plugin->ls_g, plugin->ls_distance);
glUniform3fv(r->ls_light_dir, 1, plugin->ls_light_dir);
glUniform3fv(r->ls_light_color, 1, plugin->ls_light_color);
glUniform3fv(r->ls_sun_color, 1, plugin->ls_sun_color);
setup_light_scattering(plugin);
break;
}
}
@@ -998,6 +1420,8 @@ void RE_render(struct sact_sprite *sp)
glEnable(GL_BLEND);
setup_lights(plugin);
if (re_plugin_version >= RE_SEAL_PLUGIN)
setup_tone_mapping(plugin);
// Sort instances by z-order.
struct RE_instance **sorted_instances = sort_instances(plugin, view_transform);
+1599
View File
File diff suppressed because it is too large Load Diff
+388
View File
@@ -0,0 +1,388 @@
/* Copyright (C) 2026 kichikuou <KichikuouChrome@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://gnu.org/licenses/>.
*/
#include <cglm/cglm.h>
#include "system4.h"
#include "system4/string.h"
#include "3d_internal.h"
#include "reign.h"
#include "../hll/iarray.h"
static void write_vec3(struct iarray_writer *w, vec3 v)
{
for (int i = 0; i < 3; i++)
iarray_write_float(w, v[i]);
}
static void read_vec3(struct iarray_reader *r, vec3 v)
{
for (int i = 0; i < 3; i++)
v[i] = iarray_read_float(r);
}
static void write_cstring(struct iarray_writer *w, const char *s)
{
struct string *tmp = cstr_to_string(s);
iarray_write_string(w, tmp);
free_string(tmp);
}
static void save_motion(struct iarray_writer *w, struct motion *m)
{
iarray_write(w, !!m);
if (!m)
return;
write_cstring(w, m->mot ? m->mot->name : "");
iarray_write(w, m->state);
iarray_write_float(w, m->current_frame);
iarray_write_float(w, m->frame_begin);
iarray_write_float(w, m->frame_end);
iarray_write_float(w, m->loop_frame_begin);
iarray_write_float(w, m->loop_frame_end);
}
static void load_motion(struct iarray_reader *r, struct RE_instance *inst, bool next)
{
if (!iarray_read(r))
return;
struct string *name = iarray_read_string(r);
if (name->size > 0) {
if (next)
RE_instance_load_next_motion(inst, name->text);
else
RE_instance_load_motion(inst, name->text);
}
free_string(name);
int state = iarray_read(r);
float current_frame = iarray_read_float(r);
float frame_begin = iarray_read_float(r);
float frame_end = iarray_read_float(r);
float loop_frame_begin = iarray_read_float(r);
float loop_frame_end = iarray_read_float(r);
struct motion *m = next ? inst->next_motion : inst->motion;
if (!m)
return;
m->state = state;
m->current_frame = current_frame;
m->frame_begin = frame_begin;
m->frame_end = frame_end;
m->loop_frame_begin = loop_frame_begin;
m->loop_frame_end = loop_frame_end;
}
static void save_instance(struct iarray_writer *w, struct RE_instance *inst)
{
iarray_write(w, inst->type);
iarray_write_string_or_null(w, inst->name);
for (int i = 0; i < RE_NR_INSTANCE_TARGETS; i++)
iarray_write(w, inst->target[i]);
write_vec3(w, inst->pos);
iarray_write_float(w, inst->pitch);
iarray_write_float(w, inst->roll);
iarray_write_float(w, inst->yaw);
write_vec3(w, inst->scale);
iarray_write_float(w, inst->alpha);
iarray_write_float(w, inst->grayscale_rate);
iarray_write(w, inst->draw);
iarray_write(w, inst->draw_edge);
iarray_write(w, inst->draw_shadow);
iarray_write(w, inst->make_shadow);
iarray_write(w, inst->draw_bump);
iarray_write_float(w, inst->shadow_volume_bone_radius);
iarray_write_float(w, inst->fps);
iarray_write(w, inst->use_mag_speed);
iarray_write(w, inst->motion_blend);
iarray_write_float(w, inst->motion_blend_rate);
write_vec3(w, inst->diffuse);
write_vec3(w, inst->ambient);
write_vec3(w, inst->column_pos);
iarray_write_float(w, inst->column_height);
iarray_write_float(w, inst->column_radius);
iarray_write_float(w, inst->column_angle);
iarray_write(w, inst->draw_type);
for (int i = 0; i < 4; i++)
write_vec3(w, inst->vertex_pos[i]);
for (int i = 0; i < 4; i++) {
iarray_write_float(w, inst->vertex_uv[i][0]);
iarray_write_float(w, inst->vertex_uv[i][1]);
}
write_vec3(w, inst->vec);
write_vec3(w, inst->globe_diffuse);
iarray_write(w, !!inst->light_params);
if (inst->light_params) {
iarray_write(w, RE_NR_LIGHT_PARAMS);
for (int i = 0; i < RE_NR_LIGHT_PARAMS; i++)
iarray_write_float(w, inst->light_params[i]);
}
save_motion(w, inst->motion);
save_motion(w, inst->next_motion);
}
static void load_instance(struct iarray_reader *r, struct RE_instance *inst)
{
int type = iarray_read(r);
if (type != RE_ITYPE_UNINITIALIZED)
RE_instance_set_type(inst, type);
struct string *name = iarray_read_string_or_null(r);
if (name) {
RE_instance_load(inst, name->text);
free_string(name);
}
for (int i = 0; i < RE_NR_INSTANCE_TARGETS; i++)
inst->target[i] = iarray_read(r);
read_vec3(r, inst->pos);
inst->pitch = iarray_read_float(r);
inst->roll = iarray_read_float(r);
inst->yaw = iarray_read_float(r);
read_vec3(r, inst->scale);
inst->alpha = iarray_read_float(r);
inst->grayscale_rate = iarray_read_float(r);
inst->draw = iarray_read(r);
inst->draw_edge = iarray_read(r);
inst->draw_shadow = iarray_read(r);
inst->make_shadow = iarray_read(r);
inst->draw_bump = iarray_read(r);
inst->shadow_volume_bone_radius = iarray_read_float(r);
inst->fps = iarray_read_float(r);
inst->use_mag_speed = iarray_read(r);
inst->motion_blend = iarray_read(r);
inst->motion_blend_rate = iarray_read_float(r);
read_vec3(r, inst->diffuse);
read_vec3(r, inst->ambient);
read_vec3(r, inst->column_pos);
inst->column_height = iarray_read_float(r);
inst->column_radius = iarray_read_float(r);
inst->column_angle = iarray_read_float(r);
inst->local_transform_needs_update = true;
inst->draw_type = iarray_read(r);
for (int i = 0; i < 4; i++)
read_vec3(r, inst->vertex_pos[i]);
for (int i = 0; i < 4; i++) {
inst->vertex_uv[i][0] = iarray_read_float(r);
inst->vertex_uv[i][1] = iarray_read_float(r);
}
read_vec3(r, inst->vec);
read_vec3(r, inst->globe_diffuse);
if (iarray_read(r)) {
int nr_params = iarray_read(r);
for (int i = 0; i < nr_params; i++) {
float param = iarray_read_float(r);
if (inst->light_params && i < RE_NR_LIGHT_PARAMS)
inst->light_params[i] = param;
}
}
load_motion(r, inst, false);
load_motion(r, inst, true);
}
static void save_back_cg(struct iarray_writer *w, struct RE_back_cg *bcg)
{
iarray_write_string_or_null(w, bcg->name);
iarray_write(w, bcg->no);
iarray_write_float(w, bcg->blend_rate);
iarray_write_float(w, bcg->x);
iarray_write_float(w, bcg->y);
iarray_write_float(w, bcg->mag);
iarray_write(w, bcg->show);
}
static void load_back_cg(struct iarray_reader *r, struct RE_back_cg *bcg, struct archive *aar)
{
struct string *name = iarray_read_string_or_null(r);
int no = iarray_read(r);
float blend_rate = iarray_read_float(r);
float x = iarray_read_float(r);
float y = iarray_read_float(r);
float mag = iarray_read_float(r);
bool show = iarray_read(r);
if (bcg) {
if (name)
RE_back_cg_set_name(bcg, name, aar);
else if (no)
RE_back_cg_set(bcg, no);
bcg->blend_rate = blend_rate;
bcg->x = x;
bcg->y = y;
bcg->mag = mag;
bcg->show = show;
}
if (name)
free_string(name);
}
void RE_plugin_serialize(struct RE_plugin *plugin, struct iarray_writer *w)
{
write_vec3(w, plugin->camera.pos);
iarray_write_float(w, plugin->camera.pitch);
iarray_write_float(w, plugin->camera.roll);
iarray_write_float(w, plugin->camera.yaw);
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++)
iarray_write_float(w, plugin->proj_transform[i][j]);
}
iarray_write(w, plugin->viewport_x);
iarray_write(w, plugin->viewport_y);
iarray_write(w, plugin->viewport_width);
iarray_write(w, plugin->viewport_height);
iarray_write(w, RE_NR_BACK_CGS);
for (int i = 0; i < RE_NR_BACK_CGS; i++)
save_back_cg(w, &plugin->back_cg[i]);
iarray_write(w, plugin->render_mode);
iarray_write(w, plugin->shadow_mode);
write_vec3(w, plugin->shadow_map_light_dir);
iarray_write_float(w, plugin->shadow_bias);
iarray_write(w, plugin->bump_mode);
iarray_write(w, plugin->bloom_mode);
iarray_write(w, plugin->glare_mode);
iarray_write(w, plugin->fog_mode);
iarray_write(w, plugin->fog_type);
iarray_write_float(w, plugin->fog_near);
iarray_write_float(w, plugin->fog_far);
write_vec3(w, plugin->fog_color);
iarray_write_float(w, plugin->ls_beta_r);
iarray_write_float(w, plugin->ls_beta_m);
iarray_write_float(w, plugin->ls_g);
iarray_write_float(w, plugin->ls_distance);
write_vec3(w, plugin->ls_light_dir);
write_vec3(w, plugin->ls_light_color);
write_vec3(w, plugin->ls_sun_color);
iarray_write(w, plugin->specular_mode);
iarray_write(w, plugin->light_map_mode);
iarray_write(w, plugin->soft_fog_edge_mode);
iarray_write(w, plugin->ssao_mode);
iarray_write(w, plugin->vertex_blend_mode);
iarray_write(w, plugin->shader_precision_mode);
iarray_write(w, plugin->texture_filter_mode);
iarray_write(w, plugin->use_power2_texture);
iarray_write(w, plugin->shadow_map_resolution_level);
iarray_write(w, plugin->texture_resolution_level);
write_vec3(w, plugin->global_ambient);
iarray_write_float(w, plugin->post_effect_filter_y);
iarray_write_float(w, plugin->post_effect_filter_cb);
iarray_write_float(w, plugin->post_effect_filter_cr);
iarray_write(w, RE_DRAW_OPTION_MAX);
for (int i = 0; i < RE_DRAW_OPTION_MAX; i++)
iarray_write(w, plugin->draw_options[i]);
iarray_write(w, plugin->suspended);
iarray_write(w, plugin->mag_speed);
iarray_write(w, RE_NR_LIGHT_PARAMS);
for (int i = 0; i < RE_NR_LIGHT_PARAMS; i++)
iarray_write_float(w, plugin->light_params[i]);
iarray_write_float(w, plugin->edge_length);
iarray_write_float(w, plugin->edge_reduction_rate);
write_vec3(w, plugin->edge_color);
iarray_write(w, plugin->nr_instances);
for (int i = 0; i < plugin->nr_instances; i++) {
iarray_write(w, !!plugin->instances[i]);
if (plugin->instances[i])
save_instance(w, plugin->instances[i]);
}
}
void RE_plugin_deserialize(struct RE_plugin *plugin, struct iarray_reader *r, int version)
{
read_vec3(r, plugin->camera.pos);
plugin->camera.pitch = iarray_read_float(r);
plugin->camera.roll = iarray_read_float(r);
plugin->camera.yaw = iarray_read_float(r);
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++)
plugin->proj_transform[i][j] = iarray_read_float(r);
}
int viewport_x = iarray_read(r);
int viewport_y = iarray_read(r);
int viewport_width = iarray_read(r);
int viewport_height = iarray_read(r);
if (viewport_width > 0 && viewport_height > 0)
RE_set_viewport(plugin, viewport_x, viewport_y, viewport_width, viewport_height);
int nr_back_cgs = iarray_read(r);
for (int i = 0; i < nr_back_cgs; i++)
load_back_cg(r, i < RE_NR_BACK_CGS ? &plugin->back_cg[i] : NULL, plugin->aar);
plugin->render_mode = iarray_read(r);
plugin->shadow_mode = iarray_read(r);
read_vec3(r, plugin->shadow_map_light_dir);
plugin->shadow_bias = iarray_read_float(r);
plugin->bump_mode = iarray_read(r);
plugin->bloom_mode = iarray_read(r);
plugin->glare_mode = iarray_read(r);
plugin->fog_mode = iarray_read(r);
plugin->fog_type = iarray_read(r);
plugin->fog_near = iarray_read_float(r);
plugin->fog_far = iarray_read_float(r);
read_vec3(r, plugin->fog_color);
plugin->ls_beta_r = iarray_read_float(r);
plugin->ls_beta_m = iarray_read_float(r);
plugin->ls_g = iarray_read_float(r);
plugin->ls_distance = iarray_read_float(r);
read_vec3(r, plugin->ls_light_dir);
read_vec3(r, plugin->ls_light_color);
read_vec3(r, plugin->ls_sun_color);
plugin->specular_mode = iarray_read(r);
plugin->light_map_mode = iarray_read(r);
plugin->soft_fog_edge_mode = iarray_read(r);
plugin->ssao_mode = iarray_read(r);
plugin->vertex_blend_mode = iarray_read(r);
plugin->shader_precision_mode = iarray_read(r);
plugin->texture_filter_mode = iarray_read(r);
plugin->use_power2_texture = iarray_read(r);
plugin->shadow_map_resolution_level = iarray_read(r);
plugin->texture_resolution_level = iarray_read(r);
read_vec3(r, plugin->global_ambient);
plugin->post_effect_filter_y = iarray_read_float(r);
plugin->post_effect_filter_cb = iarray_read_float(r);
plugin->post_effect_filter_cr = iarray_read_float(r);
int nr_draw_options = iarray_read(r);
for (int i = 0; i < nr_draw_options; i++) {
int option = iarray_read(r);
if (i < RE_DRAW_OPTION_MAX)
plugin->draw_options[i] = option;
}
plugin->suspended = iarray_read(r);
plugin->mag_speed = iarray_read(r);
int nr_light_params = iarray_read(r);
for (int i = 0; i < nr_light_params; i++) {
float param = iarray_read_float(r);
if (i < RE_NR_LIGHT_PARAMS)
plugin->light_params[i] = param;
}
plugin->edge_length = iarray_read_float(r);
plugin->edge_reduction_rate = iarray_read_float(r);
read_vec3(r, plugin->edge_color);
int nr_instances = iarray_read(r);
for (int i = 0; i < nr_instances; i++) {
if (!iarray_read(r))
continue;
load_instance(r, RE_create_instance_at(plugin, i));
}
}
+4 -1
View File
@@ -85,7 +85,10 @@ bool asset_manager_load_archive(enum asset_type type, const char *archive_name)
return false;
if (!assets[type]->load_archive)
ERROR("load_archive not supported on this archive type");
return assets[type]->load_archive(assets[type], archive_name);
char *path = gamedir_path(archive_name);
bool r = assets[type]->load_archive(assets[type], path);
free(path);
return r;
}
bool asset_exists(enum asset_type type, int id)
+27 -5
View File
@@ -103,7 +103,7 @@ bool audio_play_archive_data(struct archive_data *dfile)
for (int i = 0; i < NR_ANONYMOUS_CHANNELS; i++) {
if (anonymous_channels[i] == -1) {
int ch = wav_get_unused_channel();
if (!wav_prepare_from_archive_data(ch, dfile))
if (!wav_prepare_from_archive_data(ch, AUDIO_NO_METADATA, dfile))
return false;
if (!wav_play(ch)) {
wav_unprepare(ch);
@@ -361,6 +361,18 @@ static int audio_get_time_length(struct id_pool *pool, int id)
int wav_get_time_length(int id) { return audio_get_time_length(&wav, id); }
int bgm_get_time_length(int id) { return audio_get_time_length(&bgm, id); }
static int audio_get_data_length(enum asset_type type, int no) {
struct channel *ch = channel_open(type, no);
if (!ch)
return 0;
int len = channel_get_time_length(ch);
channel_close(ch);
return len;
}
int wav_get_data_length(int no) { return audio_get_data_length(ASSET_SOUND, no); }
int bgm_get_data_length(int no) { return audio_get_data_length(ASSET_BGM, no); }
static int audio_get_data_no(struct id_pool *pool, int id)
{
struct channel *ch = id_pool_get(pool, id);
@@ -392,19 +404,29 @@ static int audio_prepare_from_file(struct id_pool *pool, int id, char *filename)
int wav_prepare_from_file(int id, char *filename) { return audio_prepare_from_file(&wav, id, filename); }
int bgm_prepare_from_file(int id, char *filename) { return audio_prepare_from_file(&bgm, id, filename); }
static int audio_prepare_from_archive_data(struct id_pool *pool, int id, struct archive_data *dfile)
static int audio_prepare_from_archive_data(struct id_pool *pool, int id,
enum asset_type type, int metadata_no,
struct archive_data *dfile)
{
if (id < 0)
return 0;
struct channel *ch = channel_open_archive_data(dfile);
struct channel *ch = channel_open_archive_data(dfile, type, metadata_no);
struct channel *old = id_pool_set(pool, id, ch);
if (old)
channel_close(old);
return !!ch;
}
int wav_prepare_from_archive_data(int id, struct archive_data *dfile) {
return audio_prepare_from_archive_data(&wav, id, dfile);
int wav_prepare_from_archive_data(int id, int metadata_no, struct archive_data *dfile)
{
return audio_prepare_from_archive_data(
&wav, id, ASSET_SOUND, metadata_no, dfile);
}
int bgm_prepare_from_archive_data(int id, int metadata_no, struct archive_data *dfile)
{
return audio_prepare_from_archive_data(
&bgm, id, ASSET_BGM, metadata_no, dfile);
}
int wav_get_group_num_from_data_num(int no)
+21 -23
View File
@@ -505,33 +505,12 @@ struct channel *channel_open(enum asset_type type, int no)
return NULL;
}
struct channel *ch = channel_open_archive_data(dfile);
struct channel *ch = channel_open_archive_data(dfile, type, no);
if (!ch) {
WARNING("Failed to open %s %d", type == ASSET_SOUND ? "WAV" : "BGM", no);
return NULL;
}
if (type == ASSET_SOUND) {
struct wai *wai = wai_get(no);
ch->volume = 100;
ch->loop_start = 0;
ch->loop_end = ch->info.frames;
ch->loop_count = 1;
ch->mixer_no = wai ? wai->channel : 1;
} else {
struct bgi *bgi = bgi_get(no);
if (bgi) {
ch->volume = clamp(0, 100, bgi->volume);
ch->loop_start = clamp(0, ch->info.frames, bgi->loop_start);
ch->loop_end = clamp(0, ch->info.frames, bgi->loop_end);
ch->loop_count = max(0, bgi->loop_count);
ch->mixer_no = clamp(0, nr_mixers, bgi->channel);
} else {
ch->loop_count = 0;
}
}
ch->no = no;
return ch;
}
@@ -565,7 +544,8 @@ static bool init_channel(struct channel *ch)
return true;
}
struct channel *channel_open_archive_data(struct archive_data *dfile)
struct channel *channel_open_archive_data(struct archive_data *dfile,
enum asset_type type, int metadata_no)
{
struct channel *ch = xcalloc(1, sizeof(struct channel));
ch->dfile = dfile;
@@ -580,6 +560,24 @@ struct channel *channel_open_archive_data(struct archive_data *dfile)
free(ch);
return NULL;
}
if (metadata_no >= 0) {
if (type == ASSET_SOUND) {
struct wai *wai = wai_get(metadata_no);
ch->mixer_no = wai ? wai->channel : 1;
} else if (type == ASSET_BGM) {
struct bgi *bgi = bgi_get(metadata_no);
if (bgi) {
ch->volume = clamp(0, 100, bgi->volume);
ch->loop_start = clamp(0, ch->info.frames, bgi->loop_start);
ch->loop_end = clamp(0, ch->info.frames, bgi->loop_end);
ch->loop_count = max(0, bgi->loop_count);
ch->mixer_no = clamp(0, nr_mixers, bgi->channel);
} else {
ch->loop_count = 0;
}
}
ch->no = metadata_no;
}
return ch;
}
+20 -2
View File
@@ -56,6 +56,9 @@ static enum {
DAP_STOPPED
} dap_state = DAP_UNINITIALIZED;
static bool dap_launch_req_received = false;
static bool dap_config_done_req_received = false;
static enum {
NOT_STEPPING = 0,
STEPPING_INTO,
@@ -178,12 +181,17 @@ static void cmd_launch(cJSON *args, cJSON *resp)
// TODO: handle noDebug option
send_response(resp, true);
dap_state = DAP_RUNNING;
dap_launch_req_received = true;
if (dap_config_done_req_received)
dap_state = DAP_RUNNING;
}
static void cmd_configurationDone(cJSON *args, cJSON *resp)
{
send_response(resp, true);
dap_config_done_req_received = true;
if (dap_launch_req_received)
dap_state = DAP_RUNNING;
}
static void cmd_stackTrace(cJSON *args, cJSON *resp)
@@ -403,6 +411,8 @@ static void add_array_elements_to_array(cJSON *array, struct page *page)
static void add_page_to_array(cJSON *array, struct page *page)
{
if (!page)
return;
switch (page->type) {
case GLOBAL_PAGE:
add_globals_to_array(array, page);
@@ -1000,12 +1010,13 @@ static int read_command_thread(void *data)
WARNING("Debug Adapter Protocol error: no Content-Length header");
continue;
}
char *buf = malloc(content_length);
char *buf = malloc(content_length+1);
if (fread(buf, content_length, 1, stdin) != 1) {
WARNING("fread(stdin): %s", strerror(errno));
free(buf);
continue;
}
buf[content_length] = '\0';
msgq_enqueue(queue, buf);
content_length = -1;
} else {
@@ -1087,6 +1098,13 @@ void dbg_dap_log(const char *log, const char *fmt, va_list ap)
size_t len = max(1024, strlen(fmt) * 2);
char *buf = xmalloc(len);
vsnprintf(buf, len, fmt, ap);
#ifdef _WIN32
// Since the callers use display_sjis*(), buf is in Shift_JIS on Windows.
// Convert to UTF-8 for DAP.
char *utf8 = sjis2utf(buf, 0);
free(buf);
buf = utf8;
#endif
emit_output_event(log, buf);
free(buf);
}
+27
View File
@@ -1094,6 +1094,33 @@ void gfx_copy_grayscale(Texture *dst, int dx, int dy, Texture *src, int sx, int
restore_blend_mode();
}
void gfx_copy_grayscale_with_alpha_map(Texture *dst, int dx, int dy, Texture *src, int sx, int sy, int w, int h)
{
glBlendFuncSeparate(GL_ONE, GL_ZERO, GL_ONE, GL_ZERO);
struct copy_data data = COPY_DATA(dx, dy, sx, sy, w, h);
run_copy_shader(&copy_grayscale_shader.s, dst, src, &data);
restore_blend_mode();
}
void gfx_copy_grayscale_reverse_LR_with_alpha_map(Texture *dst, int dx, int dy, Texture *src, int sx, int sy, int w, int h)
{
mat4 mw_transform = MAT4(
-src->w, 0, 0, sx + w,
0, src->h, 0, -sy,
0, 0, 1, 0,
0, 0, 0, 1);
mat4 wv_transform = WV_TRANSFORM(w, h);
glBlendFuncSeparate(GL_ONE, GL_ZERO, GL_ONE, GL_ZERO);
struct copy_data data = COPY_DATA(dx, dy, sx, sy, w, h);
run_draw_shader(&copy_grayscale_shader.s, dst, src, mw_transform, wv_transform, &data);
restore_blend_mode();
}
static void draw_line(Texture *dst, int x0, int y0, int x1, int y1, struct copy_data *data)
{
GLfloat w = dst->w;
+2 -2
View File
@@ -60,7 +60,7 @@ struct dungeon_context *dungeon_get_context(int surface)
struct dungeon_context *dungeon_context_create(enum draw_dungeon_version version, int width, int height)
{
struct dungeon_context *ctx = xcalloc(1, sizeof(struct dungeon_context));
struct dungeon_context *ctx = xcalloc_aligned(1, struct dungeon_context);
ctx->plugin.name = plugin_name;
ctx->plugin.free = dungeon_context_free;
ctx->plugin.update = dungeon_render;
@@ -102,7 +102,7 @@ static void dungeon_context_free(struct draw_plugin *plugin)
if (ctx->map)
dungeon_map_free(ctx->map);
free(ctx);
xfree_aligned(ctx);
}
static GLuint *load_event_textures(int *nr_textures_out)
+5
View File
@@ -75,6 +75,7 @@ const char *effect_names[NR_EFFECTS] = {
[EFFECT_BLUR_FADEOUT] = "EFFECT_BLUR_FADEOUT",
[EFFECT_BLUR_CROSSFADE] = "EFFECT_BLUR_CROSSFADE",
[EFFECT_2ROT_ZOOM_BLEND_BLUR] = "EFFECT_2ROT_ZOOM_BLEND_BLUR",
[EFFECT_VWAVE_SCROLL_CROSSFADE] = "EFFECT_VWAVE_SCROLL_CROSSFADE",
[EFFECT_VWAVE_CROSSFADE] = "EFFECT_VWAVE_CROSSFADE",
};
@@ -145,6 +146,7 @@ static void render_effect_shader(struct effect_shader *shader, Texture *dst, Tex
static struct effect_shader crossfade_shader = EFFECT_SHADER("shaders/effects/crossfade.f.glsl");
static struct effect_shader crossfade_lr_shader = EFFECT_SHADER("shaders/effects/crossfade_lr.f.glsl");
static struct effect_shader crossfade_up_down_shader = EFFECT_SHADER("shaders/effects/crossfade_up_down.f.glsl");
static struct effect_shader crossfade_down_up_shader = EFFECT_SHADER("shaders/effects/crossfade_down_up.f.glsl");
static struct effect_shader mosaic_shader = EFFECT_SHADER("shaders/effects/mosaic.f.glsl");
static struct effect_shader blind_down_shader = EFFECT_SHADER("shaders/effects/blind_down.f.glsl");
static struct effect_shader blind_lr_shader = EFFECT_SHADER("shaders/effects/blind_lr.f.glsl");
@@ -155,12 +157,14 @@ static struct effect_shader turn_page_shader = EFFECT_SHADER("shaders/effects/tu
static struct effect_shader sepia_noise_crossfade_shader = EFFECT_SHADER("shaders/effects/sepia_noise_crossfade.f.glsl");
static struct effect_shader blur_fadeout_shader = EFFECT_SHADER("shaders/effects/blur_fadeout.f.glsl");
static struct effect_shader blur_crossfade_shader = EFFECT_SHADER("shaders/effects/blur_crossfade.f.glsl");
static struct effect_shader vwave_scroll_crossfade_shader = EFFECT_SHADER("shaders/effects/vwave_scroll_crossfade.f.glsl");
static struct effect_shader vwave_crossfade_shader = EFFECT_SHADER("shaders/effects/vwave_crossfade.f.glsl");
static struct effect_shader *effect_shaders[NR_EFFECTS] = {
[EFFECT_CROSSFADE] = &crossfade_shader,
[EFFECT_CROSSFADE_LR] = &crossfade_lr_shader,
[EFFECT_UP_DOWN_CROSSFADE] = &crossfade_up_down_shader,
[EFFECT_DOWN_UP_CROSSFADE] = &crossfade_down_up_shader,
[EFFECT_CROSSFADE_MOSAIC] = &mosaic_shader,
[EFFECT_BLIND_DOWN] = &blind_down_shader,
[EFFECT_BLIND_LR] = &blind_lr_shader,
@@ -171,6 +175,7 @@ static struct effect_shader *effect_shaders[NR_EFFECTS] = {
[EFFECT_SEPIA_NOISE_CROSSFADE] = &sepia_noise_crossfade_shader,
[EFFECT_BLUR_FADEOUT] = &blur_fadeout_shader,
[EFFECT_BLUR_CROSSFADE] = &blur_crossfade_shader,
[EFFECT_VWAVE_SCROLL_CROSSFADE] = &vwave_scroll_crossfade_shader,
[EFFECT_VWAVE_CROSSFADE] = &vwave_crossfade_shader,
};
+14
View File
@@ -417,6 +417,7 @@ void hll_call(int libno, int fno)
extern struct static_library lib_ACXLoader;
extern struct static_library lib_ACXLoaderP2;
extern struct static_library lib_ADVSYS;
extern struct static_library lib_AFAFactory;
extern struct static_library lib_AliceLogo;
extern struct static_library lib_AliceLogo2;
extern struct static_library lib_AliceLogo3;
@@ -440,6 +441,7 @@ extern struct static_library lib_DALKDemo;
extern struct static_library lib_DALKEDemo;
extern struct static_library lib_Data;
extern struct static_library lib_DataFile;
extern struct static_library lib_Discord;
extern struct static_library lib_DrawDungeon;
extern struct static_library lib_DrawDungeon2;
extern struct static_library lib_DrawDungeon14;
@@ -468,6 +470,7 @@ extern struct static_library lib_InputDevice;
extern struct static_library lib_InputString;
extern struct static_library lib_KiwiSoundEngine;
extern struct static_library lib_LoadCG;
extern struct static_library lib_MADLoader;
extern struct static_library lib_MainEXFile;
extern struct static_library lib_MainSurface;
extern struct static_library lib_MamanyoDemo;
@@ -483,6 +486,8 @@ extern struct static_library lib_MsgSkip;
extern struct static_library lib_MusicSystem;
extern struct static_library lib_NewFont;
extern struct static_library lib_OutputLog;
extern struct static_library lib_P3MapSprite;
extern struct static_library lib_P3SquareSprite;
extern struct static_library lib_PassRegister;
extern struct static_library lib_PastelChime2;
extern struct static_library lib_PartsEngine;
@@ -492,6 +497,7 @@ extern struct static_library lib_PlayMovie;
extern struct static_library lib_ReignEngine;
extern struct static_library lib_SACT2;
extern struct static_library lib_SACTDX;
extern struct static_library lib_SealEngine;
extern struct static_library lib_SengokuRanceFont;
extern struct static_library lib_Sound2ex;
extern struct static_library lib_SoundFilePlayer;
@@ -527,12 +533,14 @@ extern struct static_library lib_vmString;
extern struct static_library lib_vmSurface;
extern struct static_library lib_vmSystem;
extern struct static_library lib_vmTimer;
extern struct static_library lib_ValueEncryption;
extern struct static_library lib_VSFile;
static struct static_library *static_libraries[] = {
&lib_ACXLoader,
&lib_ACXLoaderP2,
&lib_ADVSYS,
&lib_AFAFactory,
&lib_AliceLogo,
&lib_AliceLogo2,
&lib_AliceLogo3,
@@ -556,6 +564,7 @@ static struct static_library *static_libraries[] = {
&lib_DALKEDemo,
&lib_Data,
&lib_DataFile,
&lib_Discord,
&lib_DrawDungeon,
&lib_DrawDungeon2,
&lib_DrawDungeon14,
@@ -584,6 +593,7 @@ static struct static_library *static_libraries[] = {
&lib_InputString,
&lib_KiwiSoundEngine,
&lib_LoadCG,
&lib_MADLoader,
&lib_MainEXFile,
&lib_MainSurface,
&lib_MamanyoDemo,
@@ -599,6 +609,8 @@ static struct static_library *static_libraries[] = {
&lib_MusicSystem,
&lib_NewFont,
&lib_OutputLog,
&lib_P3MapSprite,
&lib_P3SquareSprite,
&lib_PassRegister,
&lib_PastelChime2,
&lib_PartsEngine,
@@ -608,6 +620,7 @@ static struct static_library *static_libraries[] = {
&lib_ReignEngine,
&lib_SACT2,
&lib_SACTDX,
&lib_SealEngine,
&lib_SengokuRanceFont,
&lib_Sound2ex,
&lib_SoundFilePlayer,
@@ -643,6 +656,7 @@ static struct static_library *static_libraries[] = {
&lib_vmSurface,
&lib_vmSystem,
&lib_vmTimer,
&lib_ValueEncryption,
&lib_VSFile,
NULL
};
+13 -11
View File
@@ -101,24 +101,26 @@ static struct fnl_bitmap_glyph *fnl_get_bitmap_glyph(struct fnl_bitmap_size *bit
if (bitmap->glyphs[index])
return bitmap->glyphs[index];
unsigned long data_size;
uint8_t *data = fnl_glyph_data(bitmap->face->font->fnl, &bitmap->face->glyphs[index], &data_size);
struct fnl_glyph *glyph = &bitmap->face->glyphs[index];
uint8_t *data = fnl_glyph_data(bitmap->face->font->fnl, glyph);
const int glyph_w = (data_size*8) / bitmap->face->height;
const int glyph_w = fnl_glyph_stride(glyph) * 8;
const int glyph_h = bitmap->face->height;
struct fnl_bitmap_glyph *out = xmalloc(sizeof(struct fnl_bitmap_glyph));
out->width = glyph_w;
out->height = glyph_h;
out->advance = bitmap->face->glyphs[index].real_width;
out->pixels = xmalloc(glyph_w * glyph_h);
out->advance = glyph->width;
out->pixels = xcalloc(glyph_w, glyph_h);
// expand 1-bit bitmap to 8-bit
for (unsigned i = 0; i < glyph_w * glyph_h; i++) {
unsigned row = (glyph_h - 1) - i / glyph_w;
unsigned col = i % glyph_w;
bool on = data[i/8] & (1 << (7 - i % 8));
out->pixels[row*glyph_w + col] = on ? 255 : 0;
for (unsigned row = 0; row < glyph_h; row++) {
uint8_t *dst = out->pixels + ((glyph_h - 1) - row) * glyph_w;
for (unsigned col = 0; col < glyph->width; col++) {
unsigned i = row * glyph_w + col;
bool on = data[i/8] & (1 << (7 - i % 8));
dst[col] = on ? 255 : 0;
}
}
free(data);
@@ -183,7 +185,7 @@ static float fnl_font_size_char(struct font_size *_size, uint32_t code)
return 0.0;
if (!size->bitmap_size->face->glyphs[index].data_pos)
return 0.0;
float r = (float)size->bitmap_size->face->glyphs[index].real_width / (float)size->denominator;
float r = (float)size->bitmap_size->face->glyphs[index].width / (float)size->denominator;
if (code == '\t')
r *= 2.88;
return r;
+106
View File
@@ -51,6 +51,15 @@ static void write_instruction1(struct buffer *out, enum opcode op, int32_t arg)
buffer_write_int32(out, arg);
}
static void write_instruction3(struct buffer *out, enum opcode op, int32_t arg0, int32_t arg1,
int32_t arg2)
{
buffer_write_int16(out, op);
buffer_write_int32(out, arg0);
buffer_write_int32(out, arg1);
buffer_write_int32(out, arg2);
}
void set_msgskip_delay(struct ain *ain, unsigned ms)
{
int orig_fno = ain_get_function(ain, "A");
@@ -175,6 +184,100 @@ static void apply_rance6_hacks(struct ain *ain)
game_rance6_mg = true;
}
// Rance IX
// --------
// T_PartsNo class is implemented in an extremely inefficient way, which
// causes noticable performance issues when using the UI (e.g. when loading
// the items screen).
// We rewrite this class to improve performance.
static void apply_rance9_hacks(struct ain *ain)
{
int add_fno = ain_get_function(ain, "T_PartsNo@add");
int erase_fno = ain_get_function(ain, "T_PartsNo@erase");
if (add_fno < 0 || erase_fno < 0) {
WARNING("Couldn't location T_PartsNo@add or T_PartsNo@erase");
return;
}
struct buffer out;
buffer_init(&out, ain->code, ain->code_size);
// bool T_PartsNo::add(int nNo)
// {
// if (nNo < 10000 || nNo > 999999)
// return false;
// int idx = nNo - 10000;
// if (this.m_abUsed[idx])
// return false;
// return (this.m_abUsed[idx] = true);
// }
int32_t return_false_addr[3];
struct ain_function *f = &ain->functions[add_fno];
buffer_seek(&out, f->address);
// if (nNo < 10000 || nNo > 999999) return false;
write_instruction3(&out, SH_IF_LOC_LT_IMM, 0, 10000, 0);
return_false_addr[0] = out.index - 4;
write_instruction3(&out, SH_IF_LOC_GT_IMM, 0, 999999, 0);
return_false_addr[1] = out.index - 4;
// int idx = nNo - 10000;
write_instruction0(&out, PUSHLOCALPAGE);
write_instruction1(&out, PUSH, 1);
write_instruction1(&out, SH_LOCALREF, 0);
write_instruction1(&out, PUSH, 10000);
write_instruction0(&out, SUB);
write_instruction0(&out, ASSIGN);
write_instruction0(&out, POP);
// if (this.m_abUsed[idx]) return false;
write_instruction1(&out, SH_STRUCTREF, 1);
write_instruction1(&out, SH_LOCALREF, 1);
write_instruction0(&out, REF);
write_instruction1(&out, IFNZ, 0);
return_false_addr[2] = out.index - 4;
// return (this.m_abUsed[idx] = true);
write_instruction1(&out, SH_STRUCTREF, 1);
write_instruction1(&out, SH_LOCALREF, 1);
write_instruction1(&out, PUSH, 1);
write_instruction0(&out, ASSIGN);
write_instruction0(&out, RETURN);
// [return false]
buffer_write_int32_at(&out, return_false_addr[0], out.index);
buffer_write_int32_at(&out, return_false_addr[1], out.index);
buffer_write_int32_at(&out, return_false_addr[2], out.index);
write_instruction1(&out, PUSH, 0);
write_instruction0(&out, RETURN);
// bool T_PartsNo::erase(int nNo)
// {
// if (nNo < 10000 || nNo > 999999)
// return false;
// this.m_abUsed[nNo - 10000] = false;
// return true;
// }
f = &ain->functions[erase_fno];
buffer_seek(&out, f->address);
// if (nNo < 10000 || nNo > 999999) return false;
write_instruction3(&out, SH_IF_LOC_LT_IMM, 0, 10000, 0);
return_false_addr[0] = out.index - 4;
write_instruction3(&out, SH_IF_LOC_GT_IMM, 0, 999999, 0);
return_false_addr[1] = out.index - 4;
// this.m_abUsed[nNo - 10000] = false;
write_instruction1(&out, SH_STRUCTREF, 1);
write_instruction1(&out, SH_LOCALREF, 0);
write_instruction1(&out, PUSH, 10000);
write_instruction0(&out, SUB);
write_instruction1(&out, PUSH, 0);
write_instruction0(&out, ASSIGN);
write_instruction0(&out, POP);
// return true;
write_instruction1(&out, PUSH, 1);
write_instruction0(&out, RETURN);
// [return false]
buffer_write_int32_at(&out, return_false_addr[0], out.index);
buffer_write_int32_at(&out, return_false_addr[1], out.index);
write_instruction1(&out, PUSH, 0);
write_instruction0(&out, RETURN);
}
// Daibanchou (English fan translation)
// ------------------------------------
// Gpx2Plus.CopyStretchReduceAMap behaves differently in order to work around
@@ -203,6 +306,9 @@ void apply_game_specific_hacks(struct ain *ain)
game_rance8_mg = true;
} else if (!strcmp(game_name, "DUNGEONS&DOLLS")) {
game_dungeons_and_dolls = true;
} else if (!strcmp(game_name, "ランス9")
|| !strcmp(game_name, "Rance 9 - The Helmanian Revolution")) {
apply_rance9_hacks(ain);
}
free(game_name);
-2
View File
@@ -17,8 +17,6 @@
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <zlib.h>
#include "system4.h"
#include "system4/string.h"
#include "system4/acx.h"
+60
View File
@@ -0,0 +1,60 @@
/* Copyright (C) 2026 kichikuou <KichikuouChrome@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://gnu.org/licenses/>.
*/
#include "system4/string.h"
#include "asset_manager.h"
#include "hll.h"
static enum asset_type get_asset_type(int type)
{
switch (type) {
case 2: return ASSET_CG;
case 3: return ASSET_FLAT;
default: return -1;
}
}
HLL_WARN_UNIMPLEMENTED(false, bool, AFAFactory, IsExistArchive, struct string *ArchiveName);
bool AFAFactory_LoadArchive(int type, struct string *archive_name)
{
enum asset_type t = get_asset_type(type);
if (t < 0) {
WARNING("Unknown asset type: %d", type);
return false;
}
return asset_manager_load_archive(t, archive_name->text);
}
//int AFAFactory_GetCountOfData(int Type);
//void AFAFactory_GetTitleByIndex(int Type, int Index, struct string **Name);
//int AFAFactory_SearchTitle(int Type, struct string *Name);
//int AFAFactory_PrefixSearchTitle(int Type, struct string *Name);
//int AFAFactory_SuffixSearchTitle(int Type, struct string *Name);
//int AFAFactory_GetCountOfSearchData(int Type);
//void AFAFactory_GetSearchTitleByIndex(int Type, int Index, struct string **Name);
HLL_LIBRARY(AFAFactory,
HLL_EXPORT(IsExistArchive, AFAFactory_IsExistArchive),
HLL_EXPORT(LoadArchive, AFAFactory_LoadArchive),
HLL_TODO_EXPORT(GetCountOfData, AFAFactory_GetCountOfData),
HLL_TODO_EXPORT(GetTitleByIndex, AFAFactory_GetTitleByIndex),
HLL_TODO_EXPORT(SearchTitle, AFAFactory_SearchTitle),
HLL_TODO_EXPORT(PrefixSearchTitle, AFAFactory_PrefixSearchTitle),
HLL_TODO_EXPORT(SuffixSearchTitle, AFAFactory_SuffixSearchTitle),
HLL_TODO_EXPORT(GetCountOfSearchData, AFAFactory_GetCountOfSearchData),
HLL_TODO_EXPORT(GetSearchTitleByIndex, AFAFactory_GetSearchTitleByIndex)
);
+2
View File
@@ -276,6 +276,7 @@ bool ADVLogList_Load(struct page **data)
goto error;
}
free_log(log);
log->lines = xcalloc(nr_lines, sizeof(struct string*));
for (int i = 0; i < nr_lines; i++) {
log->lines[i] = iarray_read_string(&r);
@@ -475,6 +476,7 @@ error:
static void AnteaterADVEngine_ModuleFini(void)
{
ADVLogList_Clear();
ADVSceneKeeper_Clear();
}
+8 -9
View File
@@ -17,12 +17,11 @@
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <zlib.h>
#include "system4/file.h"
#include "system4/little_endian.h"
#include "system4/string.h"
#include "system4/mt19937int.h"
#include "system4/zlib.h"
#include "hll.h"
#include "audio.h"
@@ -47,14 +46,15 @@ static int BanMisc_SaveStruct(struct page *page, struct string *file_name)
iarray_free_writer(&out);
// compress
unsigned long compressed_size = compressBound(raw_size);
size_t compressed_size = zlib_compress_bound(raw_size);
uint8_t *buf = xmalloc(MD11_HEADER_SIZE + compressed_size);
memcpy(buf, md11_signature, sizeof(md11_signature));
LittleEndian_putDW(buf, 4, raw_size);
uint8_t *compressed = buf + MD11_HEADER_SIZE;
int r = compress2(compressed, &compressed_size, raw_buf, raw_size, Z_DEFAULT_COMPRESSION);
compressed_size = zlib_compress(compressed, compressed_size, raw_buf,
raw_size, ZLIB_DEFAULT_COMPRESSION);
free(raw_buf);
if (r != Z_OK) {
if (!compressed_size) {
free(buf);
return 0;
}
@@ -79,7 +79,7 @@ static struct page *load_md11(int struct_type, uint8_t *buf, size_t len)
WARNING("BanMisc.LoadStruct file too short");
return NULL;
}
unsigned long raw_size = LittleEndian_getDW(buf, 4);
size_t raw_size = LittleEndian_getDW(buf, 4);
if (raw_size % 4 != 0) {
WARNING("BanMisc.LoadStruct invalid file format");
return NULL;
@@ -92,9 +92,8 @@ static struct page *load_md11(int struct_type, uint8_t *buf, size_t len)
// decompress
uint8_t *raw_buf = xmalloc(raw_size);
int r = uncompress(raw_buf, &raw_size, buf, len);
if (r != Z_OK) {
WARNING("BanMisc.LoadStruct uncompress failed: %d", r);
if (!zlib_decompress_exact(raw_buf, raw_size, buf, len)) {
WARNING("BanMisc.LoadStruct uncompress failed");
free(raw_buf);
return NULL;
}
+24 -5
View File
@@ -14,9 +14,9 @@
* along with this program; if not, see <http://gnu.org/licenses/>.
*/
#include "system4/cg.h"
#include "system4/string.h"
#include "asset_manager.h"
#include "xsystem4.h"
#include "hll.h"
static bool CGManager_Init(void *imain_system, int cg_cache_size)
@@ -26,10 +26,27 @@ static bool CGManager_Init(void *imain_system, int cg_cache_size)
static bool CGManager_LoadArchive(struct string *archive_name)
{
char *name = gamedir_path(archive_name->text);
bool r = asset_manager_load_archive(ASSET_CG, name);
free(name);
return r;
return asset_manager_load_archive(ASSET_CG, archive_name->text);
}
static bool CGManager_IsExist(struct string *cg_name)
{
if (!cg_name)
return false;
return asset_exists_by_name(ASSET_CG, cg_name->text, NULL);
}
static bool CGManager_GetInfo(struct string *cg_name, int *width, int *height, int *bpp, bool *exist_pixel, bool *exist_alpha)
{
struct cg_metrics metrics;
if (!cg_name || !asset_cg_get_metrics_by_name(cg_name->text, &metrics))
return false;
*width = metrics.w;
*height = metrics.h;
*bpp = metrics.bpp;
*exist_pixel = metrics.has_pixel;
*exist_alpha = metrics.has_alpha;
return true;
}
//static int CGManager_GetCountOfDataFromArchive(void);
@@ -43,6 +60,8 @@ static bool CGManager_LoadArchive(struct string *archive_name)
HLL_LIBRARY(CGManager,
HLL_EXPORT(Init, CGManager_Init),
HLL_EXPORT(LoadArchive, CGManager_LoadArchive),
HLL_EXPORT(IsExist, CGManager_IsExist),
HLL_EXPORT(GetInfo, CGManager_GetInfo),
HLL_TODO_EXPORT(GetCountOfDataFromArchive, CGManager_GetCountOfDataFromArchive),
HLL_TODO_EXPORT(GetTitleByIndexFromArchive, CGManager_GetTitleByIndexFromArchive),
HLL_TODO_EXPORT(SearchTitleFromArchive, CGManager_SearchTitleFromArchive),
+1 -1
View File
@@ -176,7 +176,7 @@ static void charsprite_render(struct charsprite *cs)
ch[2] = 0;
}
int w = ceilf(text_style_width(&cs->ts, ch));
int w = ceilf(gfx_size_char(&cs->ts, ch));
int h = cs->ts.size + cs->ts.size/2;
sprite_init_color(&cs->sp, w, h, 0, 0, 0, 0);
sprite_get_texture(&cs->sp); // XXX: force initialization of texture
+5 -5
View File
@@ -111,8 +111,8 @@ HLL_WARN_UNIMPLEMENTED(0, int, ChipmunkSpriteEngine, SP_SetBrightness, int sp_n
HLL_WARN_UNIMPLEMENTED(0, int, ChipmunkSpriteEngine, SP_GetBrightness, int sp_no);
static bool keep_previous_view = true;
HLL_WARN_UNIMPLEMENTED((keep_previous_view = on, true), bool, ChipmunkSpriteEngine, KeepPreviousView_SetMode, bool on);
HLL_WARN_UNIMPLEMENTED(keep_previous_view, bool, ChipmunkSpriteEngine, KeepPreviousView_GetMode);
HLL_QUIET_UNIMPLEMENTED((keep_previous_view = on, true), bool, ChipmunkSpriteEngine, KeepPreviousView_SetMode, bool on);
HLL_QUIET_UNIMPLEMENTED(keep_previous_view, bool, ChipmunkSpriteEngine, KeepPreviousView_GetMode);
HLL_QUIET_UNIMPLEMENTED(false, bool, ChipmunkSpriteEngine, KeepPreviousView);
static void ChipmunkSpriteEngine_Sleep(void)
@@ -250,7 +250,7 @@ static bool ChipmunkSpriteEngine_SP_GetFontWidth(struct string *text, int *width
return true;
}
*width = gfx_size_text(&text_sprite_ts, text->text);
*width = ceilf(gfx_size_text(&text_sprite_ts, text->text));
return true;
}
@@ -314,7 +314,7 @@ HLL_LIBRARY(ChipmunkSpriteEngine,
HLL_EXPORT(SP_SetMultipleColor, ChipmunkSpriteEngine_SP_SetMultipleColor),
HLL_EXPORT(SP_SetAddColor, ChipmunkSpriteEngine_SP_SetAddColor),
HLL_EXPORT(SP_SetShow, sact_SP_SetShow),
HLL_EXPORT(SP_SetDrawMethod, sact_SP_SetDrawMethod),
HLL_EXPORT(SP_SetDrawMethod, StoatSpriteEngine_SP_SetDrawMethod),
HLL_EXPORT(SP_SetSurfaceArea, ChipmunkSpriteEngine_SP_SetSurfaceArea),
HLL_EXPORT(SP_GetSurfaceArea, ChipmunkSpriteEngine_SP_GetSurfaceArea),
HLL_EXPORT(SP_SetSpriteTransformMode, ChipmunkSpriteEngine_SP_SetSpriteTransformMode),
@@ -330,7 +330,7 @@ HLL_LIBRARY(ChipmunkSpriteEngine,
HLL_EXPORT(SP_GetZ, sact_SP_GetZ),
HLL_EXPORT(SP_GetBlendRate, sact_SP_GetBlendRate),
HLL_EXPORT(SP_GetShow, sact_SP_GetShow),
HLL_EXPORT(SP_GetDrawMethod, sact_SP_GetDrawMethod),
HLL_EXPORT(SP_GetDrawMethod, StoatSpriteEngine_SP_GetDrawMethod),
HLL_EXPORT(SP_RenderView, ChipmunkSpriteEngine_SP_RenderView),
HLL_EXPORT(SP_IsPtIn, sact_SP_IsPtIn),
HLL_EXPORT(SP_IsPtInRect, sact_SP_IsPtInRect),
+1 -1
View File
@@ -288,7 +288,7 @@ static bool ChrLoader_LoadWavFile(int sound_channel, int id)
{
int blob = chr_loader_get_blob_handle(id, 4);
struct archive_data *dfile = chr_loader_get_blob_as_archive_data(blob);
if (!dfile || !wav_prepare_from_archive_data(sound_channel, dfile)) {
if (!dfile || !wav_prepare_from_archive_data(sound_channel, AUDIO_NO_METADATA, dfile)) {
WARNING("ChrLoader.LoadWavFile %d failed", id);
return false;
}
+5 -6
View File
@@ -15,12 +15,11 @@
*/
#include <stdlib.h>
#include <zlib.h>
#include "system4/archive.h"
#include "system4/buffer.h"
#include "system4/little_endian.h"
#include "system4/string.h"
#include "system4/zlib.h"
#include "hll.h"
#include "asset_manager.h"
@@ -40,11 +39,11 @@ static int Data_Open(int num)
struct archive_data *dfile = asset_get(ASSET_DATA, num);
if (!dfile)
return 0;
unsigned long uncompressed_size = LittleEndian_getDW(dfile->data, 0);
size_t uncompressed_size = LittleEndian_getDW(dfile->data, 0);
uint8_t *buf = xmalloc(uncompressed_size);
int rv = uncompress(buf, &uncompressed_size, (uint8_t *)dfile->data + 4, dfile->size - 4);
if (rv != Z_OK) {
WARNING("%s: uncompress failed (%d)", dfile->name, rv);
if (!zlib_decompress_exact(buf, uncompressed_size,
(uint8_t *)dfile->data + 4, dfile->size - 4)) {
WARNING("%s: uncompress failed", dfile->name);
archive_free_data(dfile);
free(buf);
return 0;
+5 -6
View File
@@ -16,12 +16,11 @@
#include <string.h>
#include <stdio.h>
#include <zlib.h>
#include "system4/archive.h"
#include "system4/buffer.h"
#include "system4/mt19937int.h"
#include "system4/string.h"
#include "system4/zlib.h"
#include "hll.h"
#include "asset_manager.h"
@@ -267,7 +266,7 @@ static int DataFile_Open(int link)
}
buffer_skip(&r, 4);
unsigned long uncompressed_size = buffer_read_int32(&r);
size_t uncompressed_size = buffer_read_int32(&r);
uint32_t compressed_size = buffer_remaining(&r);
// decode compressed data
@@ -277,10 +276,10 @@ static int DataFile_Open(int link)
mt19937_xorcode(compressed_data, compressed_size, 4588163);
uint8_t *raw = xmalloc(uncompressed_size);
int rv = uncompress(raw, &uncompressed_size, compressed_data, compressed_size);
bool ok = zlib_decompress_exact(raw, uncompressed_size, compressed_data, compressed_size);
free(compressed_data);
if (rv != Z_OK) {
WARNING("Uncompress failed: %d", rv);
if (!ok) {
WARNING("Uncompress failed");
free(raw);
return 0;
}
+32
View File
@@ -0,0 +1,32 @@
/* Copyright (C) 2026 Nunuhara Cabbage <nunuhara@haniwa.technology>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://gnu.org/licenses/>.
*/
#include "hll.h"
static void Discord_Update(void)
{
}
static void Discord_UpdateActivity(struct string **pIText, struct string **pIText2)
{
}
HLL_LIBRARY(Discord,
HLL_EXPORT(Update, Discord_Update),
HLL_EXPORT(UpdateActivity, Discord_UpdateActivity)
);
+9 -10
View File
@@ -20,13 +20,12 @@
#include <string.h>
#include <time.h>
#include <sys/stat.h>
#include <zlib.h>
#include "system4.h"
#include "system4/buffer.h"
#include "system4/file.h"
#include "system4/little_endian.h"
#include "system4/string.h"
#include "system4/zlib.h"
#include "hll.h"
#include "savedata.h"
@@ -180,12 +179,13 @@ static int File_Close(void)
int r = 1;
if (current_mode == FILE_WRITE) {
size_t raw_size = contents.index;
unsigned long compressed_size = compressBound(raw_size);
size_t compressed_size = zlib_compress_bound(raw_size);
uint8_t *buf = xmalloc(4 + compressed_size);
LittleEndian_putDW(buf, 0, raw_size);
int r = compress2(buf + 4, &compressed_size, contents.buf, raw_size, Z_DEFAULT_COMPRESSION);
if (r != Z_OK) {
WARNING("File.Close: compress2 failed");
compressed_size = zlib_compress(buf + 4, compressed_size, contents.buf,
raw_size, ZLIB_DEFAULT_COMPRESSION);
if (!compressed_size) {
WARNING("File.Close: zlib_compress failed");
r = 0;
} else {
if (fwrite(buf, 4 + compressed_size, 1, current_file) != 1) {
@@ -238,11 +238,11 @@ static int File_Read(struct page **_page)
// Check if it's ZLIB compressed
if (file_size > 6 && buf[4] == 0x78 && buf[5] == 0x9c) {
unsigned long raw_size = LittleEndian_getDW(buf, 0);
size_t raw_size = LittleEndian_getDW(buf, 0);
uint8_t *raw_buf = xmalloc(raw_size);
int r = uncompress(raw_buf, &raw_size, buf + 4, file_size);
bool ok = zlib_decompress_exact(raw_buf, raw_size, buf + 4, file_size - 4);
free(buf);
if (r != Z_OK) {
if (!ok) {
WARNING("File.Read failed to uncompress");
free(raw_buf);
return 0;
@@ -368,4 +368,3 @@ HLL_LIBRARY(File,
//HLL_EXPORT(SetFind, File_SetFind),
//HLL_EXPORT(GetFind, File_GetFind),
HLL_EXPORT(MakeDirectory, File_MakeDirectory));
+8 -8
View File
@@ -897,12 +897,12 @@ HLL_LIBRARY(GUIEngine,
HLL_EXPORT(Parts_AddFillToPartsConstructionProcess, PE_AddFillToPartsConstructionProcess),
HLL_EXPORT(Parts_AddFillAlphaColorToPartsConstructionProcess, PE_AddFillAlphaColorToPartsConstructionProcess),
HLL_EXPORT(Parts_AddFillAMapToPartsConstructionProcess, PE_AddFillAMapToPartsConstructionProcess),
HLL_TODO_EXPORT(Parts_AddFillWithAlphaToPartsConstructionProcess, GUIEngine_Parts_AddFillWithAlphaToPartsConstructionProcess),
HLL_EXPORT(Parts_AddFillWithAlphaToPartsConstructionProcess, PE_AddFillWithAlphaToPartsConstructionProcess),
HLL_TODO_EXPORT(Parts_AddFillGradationHorizonToPartsConstructionProcess, GUIEngine_Parts_AddFillGradationHorizonToPartsConstructionProcess),
HLL_EXPORT(Parts_AddDrawRectToPartsConstructionProcess, PE_AddDrawRectToPartsConstructionProcess),
HLL_EXPORT(Parts_AddDrawCutCGToPartsConstructionProcess, PE_AddDrawCutCGToPartsConstructionProcess),
HLL_EXPORT(Parts_AddCopyCutCGToPartsConstructionProcess, PE_AddCopyCutCGToPartsConstructionProcess),
HLL_TODO_EXPORT(Parts_AddGrayFilterToPartsConstructionProcess, GUIEngine_Parts_AddGrayFilterToPartsConstructionProcess),
HLL_EXPORT(Parts_AddGrayFilterToPartsConstructionProcess, PE_AddGrayFilterToPartsConstructionProcess),
HLL_TODO_EXPORT(Parts_AddAddFilterToPartsConstructionProcess, GUIEngine_Parts_AddAddFilterToPartsConstructionProcess),
HLL_TODO_EXPORT(Parts_AddMulFilterToPartsConstructionProcess, GUIEngine_Parts_AddMulFilterToPartsConstructionProcess),
HLL_TODO_EXPORT(Parts_AddDrawLineToPartsConstructionProcess, GUIEngine_Parts_AddDrawLineToPartsConstructionProcess),
@@ -910,15 +910,15 @@ HLL_LIBRARY(GUIEngine,
HLL_EXPORT(Parts_AddDrawTextToPartsConstructionProcess, PE_AddDrawTextToPartsConstructionProcess),
HLL_EXPORT(Parts_AddCopyTextToPartsConstructionProcess, PE_AddCopyTextToPartsConstructionProcess),
HLL_EXPORT(Parts_SetPartsConstructionSurfaceArea, PE_SetPartsConstructionSurfaceArea),
HLL_TODO_EXPORT(Parts_CreateParts3DLayerPluginID, GUIEngine_Parts_CreateParts3DLayerPluginID),
HLL_TODO_EXPORT(Parts_GetParts3DLayerPluginID, GUIEngine_Parts_GetParts3DLayerPluginID),
HLL_TODO_EXPORT(Parts_ReleaseParts3DLayerPluginID, GUIEngine_Parts_ReleaseParts3DLayerPluginID),
HLL_EXPORT(Parts_CreateParts3DLayerPluginID, PE_CreateParts3DLayerPluginID),
HLL_EXPORT(Parts_GetParts3DLayerPluginID, PE_GetParts3DLayerPluginID),
HLL_EXPORT(Parts_ReleaseParts3DLayerPluginID, PE_ReleaseParts3DLayerPluginID),
HLL_EXPORT(Parts_SetPos, PE_SetPos),
HLL_EXPORT(Parts_SetZ, PE_SetZ),
HLL_EXPORT(Parts_SetShow, PE_SetShow),
HLL_EXPORT(Parts_SetAlpha, PE_SetAlpha),
HLL_EXPORT(Parts_SetPartsDrawFilter, PE_SetPartsDrawFilter),
HLL_TODO_EXPORT(Parts_SetPassCursor, GUIEngine_Parts_SetPassCursor),
HLL_EXPORT(Parts_SetPassCursor, PE_SetPassCursor),
HLL_EXPORT(Parts_SetClickable, PE_SetClickable),
HLL_EXPORT(Parts_SetSpeedupRateByMessageSkip, PE_SetSpeedupRateByMessageSkip),
HLL_TODO_EXPORT(Parts_SetResetTimerByChangeInputStatus, GUIEngine_Parts_SetResetTimerByChangeInputStatus),
@@ -937,7 +937,7 @@ HLL_LIBRARY(GUIEngine,
HLL_EXPORT(Parts_SetPartsRotateX, PE_SetPartsRotateX),
HLL_EXPORT(Parts_SetPartsRotateY, PE_SetPartsRotateY),
HLL_EXPORT(Parts_SetPartsRotateZ, PE_SetPartsRotateZ),
HLL_TODO_EXPORT(Parts_SetPartsAlphaClipperPartsNumber, GUIEngine_Parts_SetPartsAlphaClipperPartsNumber),
HLL_EXPORT(Parts_SetPartsAlphaClipperPartsNumber, PE_SetPartsAlphaClipperPartsNumber),
HLL_EXPORT(Parts_SetPartsPixelDecide, PE_SetPartsPixelDecide),
HLL_EXPORT(Parts_GetPartsX, PE_GetPartsX),
HLL_EXPORT(Parts_GetPartsY, PE_GetPartsY),
@@ -945,7 +945,7 @@ HLL_LIBRARY(GUIEngine,
HLL_EXPORT(Parts_GetPartsShow, PE_GetPartsShow),
HLL_EXPORT(Parts_GetPartsAlpha, PE_GetPartsAlpha),
HLL_EXPORT(Parts_GetPartsDrawFilter, PE_GetPartsDrawFilter),
HLL_TODO_EXPORT(Parts_GetPartsPassCursor, GUIEngine_Parts_GetPartsPassCursor),
HLL_EXPORT(Parts_GetPartsPassCursor, PE_GetPartsPassCursor),
HLL_EXPORT(Parts_GetPartsClickable, PE_GetPartsClickable),
HLL_TODO_EXPORT(Parts_GetPartsSpeedupRateByMessageSkip, GUIEngine_Parts_GetPartsSpeedupRateByMessageSkip),
HLL_TODO_EXPORT(Parts_GetResetTimerByChangeInputStatus, GUIEngine_Parts_GetResetTimerByChangeInputStatus),
+2 -2
View File
@@ -171,7 +171,7 @@ HLL_LIBRARY(GoatGUIEngine,
HLL_EXPORT(SetPartsRotateX, GoatGUIEngine_SetPartsRotateX),
HLL_EXPORT(SetPartsRotateY, GoatGUIEngine_SetPartsRotateY),
HLL_EXPORT(SetPartsRotateZ, GoatGUIEngine_SetPartsRotateZ),
HLL_TODO_EXPORT(SetPartsAlphaClipperPartsNumber, PE_SetPartsAlphaClipperPartsNumber),
HLL_EXPORT(SetPartsAlphaClipperPartsNumber, PE_SetPartsAlphaClipperPartsNumber),
HLL_EXPORT(SetPartsPixelDecide, PE_SetPartsPixelDecide),
HLL_EXPORT(SetThumbnailReductionSize, PE_SetThumbnailReductionSize),
HLL_EXPORT(SetThumbnailMode, PE_SetThumbnailMode),
@@ -189,7 +189,7 @@ HLL_LIBRARY(GoatGUIEngine,
HLL_EXPORT(AddFillToPartsConstructionProcess, PE_AddFillToPartsConstructionProcess),
HLL_EXPORT(AddFillAlphaColorToPartsConstructionProcess, PE_AddFillAlphaColorToPartsConstructionProcess),
HLL_EXPORT(AddFillAMapToPartsConstructionProcess, PE_AddFillAMapToPartsConstructionProcess),
HLL_TODO_EXPORT(AddFillWithAlphaToPartsConstructionProcess, PE_AddFillWithAlphaToPartsConstructionProcess),
HLL_EXPORT(AddFillWithAlphaToPartsConstructionProcess, PE_AddFillWithAlphaToPartsConstructionProcess),
HLL_TODO_EXPORT(AddFillGradationHorizonToPartsConstructionProcess, PE_AddFillGradationHorizonToPartsConstructionProcess),
HLL_EXPORT(AddDrawRectToPartsConstructionProcess, PE_AddDrawRectToPartsConstructionProcess),
HLL_EXPORT(BuildPartsConstructionProcess, PE_BuildPartsConstructionProcess),
+35 -2
View File
@@ -17,9 +17,11 @@
#include <SDL.h>
#include "system4.h"
#include "system4/ain.h"
#include "system4/utfsjis.h"
#include "system4/string.h"
#include "vm.h"
#include "gfx/types.h"
#include "gfx/gfx.h"
#include "hll.h"
@@ -29,6 +31,7 @@ static int font_size;
static int font_weight;
static Point pos;
static bool has_editing_text;
static bool get_result_string_clears;
static struct string *result;
@@ -39,13 +42,21 @@ static void InputString_ClearResultString(void)
result = string_ref(&EMPTY_STRING);
}
struct string *InputString_GetResultString(void)
static struct string *InputString_GetResultString(void)
{
struct string *s = string_ref(result);
InputString_ClearResultString();
if (get_result_string_clears)
InputString_ClearResultString();
return s;
}
static void InputString_SetResultString(struct string *s)
{
if (result)
free_string(result);
result = string_ref(s);
}
static void InputString_SetFont(int nSize, possibly_unused struct string *pIName, int nWeight)
{
font_size = nSize;
@@ -84,10 +95,17 @@ static void handle_editing(const char *text, int start, int length)
has_editing_text = *text != '\0';
}
static void handle_key(int code)
{
if (code == VK_BACK && !has_editing_text && result && result->size > 0)
string_pop_back(&result);
}
static void InputString_OpenIME(void)
{
register_input_handler(handle_input);
register_editing_handler(handle_editing);
register_key_handler(handle_key);
has_editing_text = false;
SDL_StartTextInput();
}
@@ -98,6 +116,7 @@ static void InputString_CloseIME(void)
has_editing_text = false;
clear_input_handler();
clear_editing_handler();
clear_key_handler();
}
static bool InputString_Inputs(void)
@@ -110,7 +129,20 @@ static bool InputString_Converts(void)
return has_editing_text;
}
static void InputString_PreLink(void)
{
get_result_string_clears = true;
int libno = ain_get_library(ain, "InputString");
if (libno < 0)
return;
if (ain_get_library_function(ain, libno, "SetResultString") >= 0 ||
ain_get_library_function(ain, libno, "Inputs") >= 0) {
get_result_string_clears = false;
}
}
HLL_LIBRARY(InputString,
HLL_EXPORT(_PreLink, InputString_PreLink),
HLL_EXPORT(SetFont, InputString_SetFont),
HLL_EXPORT(SetPos, InputString_SetPos),
HLL_EXPORT(Begin, InputString_Begin),
@@ -119,5 +151,6 @@ HLL_LIBRARY(InputString,
HLL_EXPORT(CloseIME, InputString_CloseIME),
HLL_EXPORT(GetResultString, InputString_GetResultString),
HLL_EXPORT(ClearResultString, InputString_ClearResultString),
HLL_EXPORT(SetResultString, InputString_SetResultString),
HLL_EXPORT(Inputs, InputString_Inputs),
HLL_EXPORT(Converts, InputString_Converts));
+57 -3
View File
@@ -14,16 +14,20 @@
* along with this program; if not, see <http://gnu.org/licenses/>.
*/
#include "system4.h"
#include <stdio.h>
#include "system4.h"
#include "system4/ain.h"
#include "vm.h"
#include "hll.h"
#include "asset_manager.h"
#include "audio.h"
HLL_WARN_UNIMPLEMENTED( , void, KiwiSoundEngine, SetGlobalFocus, possibly_unused int nNum);
//int KiwiSoundEngine_Sound_GetGroupNum(int nCh);
//static bool KiwiSoundEngine_Sound_PrepareFromFile(int ch, struct string *filename);
//static int KiwiSoundEngine_Sound_GetDataLength(int data_number);
static int KiwiSoundEngine_GetMasterGroup(void)
{
@@ -55,7 +59,44 @@ static int KiwiSoundEngine_GetBackVoiceGroup(void)
return 0;
}
static bool rance9_bgm_exists(int num)
{
char name[16];
snprintf(name, sizeof(name), "%05d", num);
return asset_exists_by_name(ASSET_BGM, name, NULL);
}
static int rance9_bgm_prepare(int ch, int num)
{
char name[16];
snprintf(name, sizeof(name), "%05d", num);
struct archive_data *dfile = asset_get_by_name(ASSET_BGM, name, NULL);
if (!dfile)
return 0;
return bgm_prepare_from_archive_data(ch, num, dfile);
}
static bool rance9_wav_exists(int num)
{
char name[16];
snprintf(name, sizeof(name), "%05d", num);
return asset_exists_by_name(ASSET_SOUND, name, NULL);
}
static int rance9_wav_prepare(int ch, int num)
{
char name[16];
snprintf(name, sizeof(name), "%05d", num);
struct archive_data *dfile = asset_get_by_name(ASSET_SOUND, name, NULL);
if (!dfile)
return 0;
return wav_prepare_from_archive_data(ch, num, dfile);
}
static void KiwiSoundEngine_PreLink(void);
HLL_LIBRARY(KiwiSoundEngine,
HLL_EXPORT(_PreLink, KiwiSoundEngine_PreLink),
HLL_EXPORT(SetGlobalFocus, KiwiSoundEngine_SetGlobalFocus),
HLL_EXPORT(Music_IsExist, bgm_exists),
HLL_EXPORT(Music_Prepare, bgm_prepare),
@@ -93,7 +134,7 @@ HLL_LIBRARY(KiwiSoundEngine,
HLL_TODO_EXPORT(Sound_GetGroupNum, KiwiSoundEngine_Sound_GetGroupNum),
HLL_EXPORT(Sound_GetGroupNumFromDataNum, wav_get_group_num_from_data_num),
HLL_TODO_EXPORT(Sound_PrepareFromFile, KiwiSoundEngine_Sound_PrepareFromFile),
HLL_TODO_EXPORT(Sound_GetDataLength, KiwiSoundEngine_Sound_GetDataLength),
HLL_EXPORT(Sound_GetDataLength, wav_get_data_length),
HLL_EXPORT(GetMasterGroup, KiwiSoundEngine_GetMasterGroup),
HLL_EXPORT(GetBGMGroup, KiwiSoundEngine_GetBGMGroup),
HLL_EXPORT(GetSEGroup, KiwiSoundEngine_GetSEGroup),
@@ -101,3 +142,16 @@ HLL_LIBRARY(KiwiSoundEngine,
HLL_EXPORT(GetGimicSEGroup, KiwiSoundEngine_GetGimicSEGroup),
HLL_EXPORT(GetBackVoiceGroup, KiwiSoundEngine_GetBackVoiceGroup)
);
static void KiwiSoundEngine_PreLink(void)
{
int libno = ain_get_library(ain, "KiwiSoundEngine");
if (libno < 0)
return;
if (ain_get_library_function(ain, libno, "Sound_Seek") >= 0) {
static_library_replace(&lib_KiwiSoundEngine, "Music_IsExist", rance9_bgm_exists);
static_library_replace(&lib_KiwiSoundEngine, "Music_Prepare", rance9_bgm_prepare);
static_library_replace(&lib_KiwiSoundEngine, "Sound_IsExist", rance9_wav_exists);
static_library_replace(&lib_KiwiSoundEngine, "Sound_Prepare", rance9_wav_prepare);
}
}
+336
View File
@@ -0,0 +1,336 @@
/* Copyright (C) 2026 kichikuou <KichikuouChrome@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "system4/little_endian.h"
#include "system4/file.h"
#include "system4/string.h"
#include "system4/cg.h"
#include "hll.h"
#include "id_pool.h"
#include "xsystem4.h"
#include "gfx/gfx.h"
#include "sact.h"
// A MAD file consists of this header, followed by `nr_images` QNT images, each
// prefixed with its size.
struct MADHeader {
char magic[4]; // "MAD\0"
unsigned char unknown1[8];
int width;
int height;
int total_time;
int nr_images;
int draw_method;
int is_loop;
unsigned char unknown2[8];
int keep_last_frame;
};
struct MAD {
struct MADHeader header;
Texture textures[];
};
static struct id_pool pool;
static void MADLoader_ModuleInit(void)
{
id_pool_init(&pool, 0);
}
static void MADLoader_Close(int handle)
{
struct MAD *mad = id_pool_release(&pool, handle);
if (!mad)
return;
for (int i = 0; i < mad->header.nr_images; i++) {
if (mad->textures[i].handle)
gfx_delete_texture(&mad->textures[i]);
}
free(mad);
}
static void MADLoader_Delete(void)
{
for (int i = id_pool_get_first(&pool); i >= 0; i = id_pool_get_next(&pool, i))
MADLoader_Close(i);
}
static void MADLoader_ModuleFini(void)
{
MADLoader_Delete();
id_pool_delete(&pool);
}
static FILE *mad_open(struct string *filename, struct MADHeader *header)
{
char *path = gamedir_path(filename->text);
FILE *fp = file_open_utf8(path, "rb");
free(path);
if (!fp)
return NULL;
uint8_t buf[48];
if (fread(buf, sizeof buf, 1, fp) != 1 || memcmp(buf, "MAD", 4)) {
fclose(fp);
return NULL;
}
memcpy(header->magic, buf, 4);
memcpy(header->unknown1, buf + 4, 8);
header->width = LittleEndian_getDW(buf, 12);
header->height = LittleEndian_getDW(buf, 16);
header->total_time = LittleEndian_getDW(buf, 20);
header->nr_images = LittleEndian_getDW(buf, 24);
header->draw_method = LittleEndian_getDW(buf, 28);
header->is_loop = LittleEndian_getDW(buf, 32);
memcpy(header->unknown2, buf + 36, 8);
header->keep_last_frame = LittleEndian_getDW(buf, 44);
return fp;
}
static bool mad_read_header(struct string *filename, struct MADHeader *header)
{
FILE *fp = mad_open(filename, header);
fclose(fp);
return fp != NULL;
}
static int MADLoader_Open(struct string *filename)
{
struct MADHeader header;
FILE *fp = mad_open(filename, &header);
if (!fp)
return -1;
struct MAD *mad = xcalloc(1, sizeof(struct MAD) + header.nr_images * sizeof(Texture));
mad->header = header;
for (int i = 0; i < mad->header.nr_images; i++) {
uint8_t buf[4];
if (fread(buf, 4, 1, fp) != 1)
VM_ERROR("MADLoader: failed to read image size %d of %s", i, display_sjis0(filename->text));
uint32_t image_size = LittleEndian_getDW(buf, 0);
uint8_t *image_data = xmalloc(image_size);
if (fread(image_data, image_size, 1, fp) != 1)
VM_ERROR("MADLoader: failed to read image data %d of %s", i, display_sjis0(filename->text));
struct cg *cg = cg_load_buffer(image_data, image_size);
if (!cg)
VM_ERROR("MADLoader: failed to load cg %d of %s", i, display_sjis0(filename->text));
gfx_init_texture_with_cg(&mad->textures[i], cg);
cg_free(cg);
free(image_data);
}
fclose(fp);
int handle = id_pool_get_unused(&pool);
id_pool_set(&pool, handle, mad);
return handle;
}
static bool MADLoader_GetSize(int handle, int *width, int *height)
{
struct MAD *mad = id_pool_get(&pool, handle);
if (!mad)
return false;
*width = mad->header.width;
*height = mad->header.height;
return true;
}
static int MADLoader_GetNumof(int handle)
{
struct MAD *mad = id_pool_get(&pool, handle);
return mad ? mad->header.nr_images : -1;
}
static int MADLoader_GetTotalTime(int handle)
{
struct MAD *mad = id_pool_get(&pool, handle);
return mad ? mad->header.total_time : -1;
}
static int MADLoader_GetDrawMethod(int handle)
{
struct MAD *mad = id_pool_get(&pool, handle);
return mad ? mad->header.draw_method : -1;
}
static bool MADLoader_IsLoop(int handle)
{
struct MAD *mad = id_pool_get(&pool, handle);
return mad && mad->header.is_loop == 1;
}
static bool MADLoader_IsKeepLastFrame(int handle)
{
struct MAD *mad = id_pool_get(&pool, handle);
return mad && mad->header.keep_last_frame == 1;
}
static bool MADLoader_IsLoading(int handle)
{
// We don't support background loading.
return false;
}
static int MADLoader_GetLoadNumof(int handle)
{
struct MAD *mad = id_pool_get(&pool, handle);
return mad ? mad->header.nr_images : 0;
}
static bool MADLoader_GetSizeFromFile(struct string *filename, int *width, int *height)
{
struct MADHeader header;
if (!mad_read_header(filename, &header))
return false;
*width = header.width;
*height = header.height;
return true;
}
static int MADLoader_GetNumofFromFile(struct string *filename)
{
struct MADHeader header;
if (!mad_read_header(filename, &header))
return -1;
return header.nr_images;
}
static int MADLoader_GetTotalTimeFromFile(struct string *filename)
{
struct MADHeader header;
if (!mad_read_header(filename, &header))
return -1;
return header.total_time;
}
static int MADLoader_GetDrawMethodFromFile(struct string *filename)
{
struct MADHeader header;
if (!mad_read_header(filename, &header))
return -1;
return header.draw_method;
}
static bool MADLoader_IsLoopFromFile(struct string *filename)
{
struct MADHeader header;
if (!mad_read_header(filename, &header))
return false;
return header.is_loop == 1;
}
static bool MADLoader_IsKeepLastFrameFromFile(struct string *filename)
{
struct MADHeader header;
if (!mad_read_header(filename, &header))
return false;
return header.keep_last_frame == 1;
}
static Texture *get_mad_texture(int handle, int index)
{
struct MAD *mad = id_pool_get(&pool, handle);
if (!mad || index < 0 || index >= mad->header.nr_images)
return NULL;
return &mad->textures[index];
}
static Texture *get_dest_texture(int sp_no)
{
struct sact_sprite *sp = sact_get_sprite(sp_no);
if (!sp)
return NULL;
sprite_dirty(sp);
return sprite_get_texture(sp);
}
static bool MADLoader_Copy(int handle, int sprite, int index)
{
Texture *src = get_mad_texture(handle, index);
Texture *dst = get_dest_texture(sprite);
if (!src || !dst)
return false;
gfx_copy_with_alpha_map(dst, 0, 0, src, 0, 0, src->w, src->h);
return true;
}
static bool MADLoader_CopyReverseLR(int handle, int sprite, int index)
{
Texture *src = get_mad_texture(handle, index);
Texture *dst = get_dest_texture(sprite);
if (!src || !dst)
return false;
gfx_copy_reverse_LR_with_alpha_map(dst, 0, 0, src, 0, 0, src->w, src->h);
return true;
}
static bool MADLoader_CopyGrayscale(int handle, int sprite, int index)
{
Texture *src = get_mad_texture(handle, index);
Texture *dst = get_dest_texture(sprite);
if (!src || !dst)
return false;
gfx_copy_grayscale_with_alpha_map(dst, 0, 0, src, 0, 0, src->w, src->h);
return true;
}
static bool MADLoader_CopyGrayscaleReverseLR(int handle, int sprite, int index)
{
Texture *src = get_mad_texture(handle, index);
Texture *dst = get_dest_texture(sprite);
if (!src || !dst)
return false;
gfx_copy_grayscale_reverse_LR_with_alpha_map(dst, 0, 0, src, 0, 0, src->w, src->h);
return true;
}
HLL_LIBRARY(MADLoader,
HLL_EXPORT(_ModuleInit, MADLoader_ModuleInit),
HLL_EXPORT(_ModuleFini, MADLoader_ModuleFini),
HLL_EXPORT(Delete, MADLoader_Delete),
HLL_EXPORT(Open, MADLoader_Open),
HLL_EXPORT(Close, MADLoader_Close),
HLL_EXPORT(GetSize, MADLoader_GetSize),
HLL_EXPORT(GetNumof, MADLoader_GetNumof),
HLL_EXPORT(GetTotalTime, MADLoader_GetTotalTime),
HLL_EXPORT(GetDrawMethod, MADLoader_GetDrawMethod),
HLL_EXPORT(IsLoop, MADLoader_IsLoop),
HLL_EXPORT(IsKeepLastFrame, MADLoader_IsKeepLastFrame),
HLL_EXPORT(IsLoading, MADLoader_IsLoading),
HLL_EXPORT(GetLoadNumof, MADLoader_GetLoadNumof),
HLL_EXPORT(GetSizeFromFile, MADLoader_GetSizeFromFile),
HLL_EXPORT(GetNumofFromFile, MADLoader_GetNumofFromFile),
HLL_EXPORT(GetTotalTimeFromFile, MADLoader_GetTotalTimeFromFile),
HLL_EXPORT(GetDrawMethodFromFile, MADLoader_GetDrawMethodFromFile),
HLL_EXPORT(IsLoopFromFile, MADLoader_IsLoopFromFile),
HLL_EXPORT(IsKeepLastFrameFromFile, MADLoader_IsKeepLastFrameFromFile),
HLL_EXPORT(Copy, MADLoader_Copy),
HLL_EXPORT(CopyReverseLR, MADLoader_CopyReverseLR),
HLL_EXPORT(CopyGrayscale, MADLoader_CopyGrayscale),
HLL_EXPORT(CopyGrayscaleReverseLR, MADLoader_CopyGrayscaleReverseLR)
);
+9 -6
View File
@@ -51,7 +51,7 @@ static int set_indices(struct ex_value *val, int id)
break;
case EX_LIST:
for (unsigned i = 0; i < val->list->nr_items; i++) {
id = set_indices(&val->list->items->value, id);
id = set_indices(&val->list->items[i].value, id);
}
break;
case EX_TREE:
@@ -86,7 +86,7 @@ static void map_handles(struct ex_value *val)
break;
case EX_LIST:
for (unsigned i = 0; i < val->list->nr_items; i++) {
map_handles(&val->list->items->value);
map_handles(&val->list->items[i].value);
}
break;
case EX_TREE:
@@ -130,7 +130,7 @@ static void MainEXFile_ModuleFini(void)
nr_handles = 0;
}
//static bool MainEXFile_ReloadDebugEXFile(void);
HLL_WARN_UNIMPLEMENTED(false, bool, MainEXFile, ReloadDebugEXFile, void);
/*
* Get handle for top-level value.
@@ -225,13 +225,16 @@ static int MainEXFile_RA2Handle(int handle, int row, struct string *format_name)
static int MainEXFile_Row(int handle)
{
struct ex_table *t = handle_to_table(handle);
return t ? t->nr_rows : 0; // FIXME: should this be nr_columns?
return t ? t->nr_rows : 0;
}
static int MainEXFile_Col(int handle)
{
if (handle > 0 && (unsigned)handle < nr_handles &&
handles[handle]->type == EX_LIST)
return handles[handle]->list->nr_items;
struct ex_table *t = handle_to_table(handle);
return t ? t->nr_columns : 0; // FIXME: should this be nr_rows?
return t ? t->nr_columns : 0;
}
static int MainEXFile_Type(int handle)
@@ -663,7 +666,7 @@ static bool MainEXFile_GetEXName(struct string *tree_path, int index, struct str
HLL_LIBRARY(MainEXFile,
HLL_EXPORT(_ModuleInit, MainEXFile_ModuleInit),
HLL_EXPORT(_ModuleFini, MainEXFile_ModuleFini),
HLL_TODO_EXPORT(ReloadDebugEXFile, MainEXFile_ReloadDebugEXFile),
HLL_EXPORT(ReloadDebugEXFile, MainEXFile_ReloadDebugEXFile),
HLL_EXPORT(Handle, MainEXFile_Handle),
HLL_EXPORT(AHandle, MainEXFile_AHandle),
HLL_EXPORT(A2Handle, MainEXFile_A2Handle),
+2 -1
View File
@@ -15,10 +15,11 @@
*/
#include "hll.h"
#include "reign.h"
static bool MarmotModelEngine_Update(void)
{
// ???
ReignEngine_update_models();
return true;
}
+32 -4
View File
@@ -23,9 +23,23 @@
#define M_PI (3.14159265358979323846)
#endif
#include "system4/mt19937int.h"
#include "hll.h"
#include "vm/page.h"
static struct mt19937 mt;
static bool mt_initialized;
static uint32_t math_genrand(void)
{
if (!mt_initialized) {
mt19937_init(&mt, 4357);
mt_initialized = true;
}
return mt19937_genrand(&mt);
}
struct shuffle_table {
int id;
struct shuffle_table *next;
@@ -64,9 +78,16 @@ static float Math_Tan(float x)
return tanf(deg2rad(x));
}
static void Math_SetSeed(int seed)
{
mt19937_init(&mt, (uint32_t)seed | 1);
mt19937_genrand(&mt); // the original engine discards the first value
mt_initialized = true;
}
static void Math_SetSeedByCurrentTime(void)
{
srand(time(NULL));
Math_SetSeed(time(NULL));
}
static int Math_Min(int a, int b)
@@ -105,9 +126,14 @@ static void Math_SwapF(float *a, float *b)
//void Math_SetRandMode(int mode);
static int Math_Rand(void)
{
return math_genrand() & 0x7fffffff;
}
static float Math_RandF(void)
{
return rand() * (1.0 / (RAND_MAX + 1U));
return (float)(math_genrand() * (1.0 / 4294967296.0));
}
static void shuffle_array(int *a, int len)
@@ -187,15 +213,17 @@ HLL_LIBRARY(Math,
HLL_EXPORT(Sin, Math_Sin),
HLL_EXPORT(Tan, Math_Tan),
HLL_EXPORT(Sqrt, sqrtf),
HLL_EXPORT(Asin, asinf),
HLL_EXPORT(Acos, acosf),
HLL_EXPORT(Atan, atanf),
HLL_EXPORT(Atan2, atan2f),
HLL_EXPORT(Abs, abs),
HLL_EXPORT(AbsF, fabsf),
HLL_EXPORT(Pow, powf),
HLL_EXPORT(SetSeed, srand),
HLL_EXPORT(SetSeed, Math_SetSeed),
HLL_EXPORT(SetSeedByCurrentTime, Math_SetSeedByCurrentTime),
//HLL_EXPORT(SetRandMode, Math_SetRandMode),
HLL_EXPORT(Rand, rand),
HLL_EXPORT(Rand, Math_Rand),
HLL_EXPORT(RandF, Math_RandF),
HLL_EXPORT(RandTableInit, Math_RandTableInit),
HLL_EXPORT(RandTable, Math_RandTable),
+13
View File
@@ -14,6 +14,7 @@
* along with this program; if not, see <http://gnu.org/licenses/>.
*/
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
@@ -149,7 +150,19 @@ HLL_WARN_UNIMPLEMENTED( , void, MsgSkip, UseFlag, int use);
HLL_WARN_UNIMPLEMENTED(0, int, MsgSkip, GetNumofMsg, void);
HLL_WARN_UNIMPLEMENTED(0, int, MsgSkip, GetNumofFlag, void);
static void MsgSkip_PreLink(void)
{
int libno = ain_get_library(ain, "MsgSkip");
assert(libno >= 0);
if (ain_get_library_function(ain, libno, "Init") < 0) {
struct string *name = cstr_to_string("MsgSkip.asd");
MsgSkip_Init(name);
free_string(name);
}
}
HLL_LIBRARY(MsgSkip,
HLL_EXPORT(_PreLink, MsgSkip_PreLink),
HLL_EXPORT(Init, MsgSkip_Init),
HLL_EXPORT(UseFlag, MsgSkip_UseFlag),
HLL_EXPORT(SetEnable, MsgSkip_SetEnable),
+121
View File
@@ -0,0 +1,121 @@
/* Copyright (C) 2026 kichikuou <KichikuouChrome@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://gnu.org/licenses/>.
*/
#include "gfx/gfx.h"
#include "vm/page.h"
#include "hll.h"
#include "sact.h"
#include "xsystem4.h"
static struct {
Texture bg;
Texture grid;
int blend_rate;
} p3map;
// Initializes the grid texture with isometric tiles based on the provided array.
static void init_grid_texture(Texture *tile, int offset_x, int offset_y, int rows, int cols, struct page *array)
{
if (array->nr_vars != rows * cols)
VM_ERROR("Expected %d variables in array, got %d", rows * cols, array->nr_vars);
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
if (array->values[row * cols + col].i != 0)
continue;
// Isometric Coordinates.
int x = ((col - row) - 1) * (tile->w / 2) + offset_x;
int y = (col + row) * (tile->h / 2) + offset_y;
gfx_copy_with_alpha_map(&p3map.grid, x, y, tile, 0, 0, tile->w, tile->h);
}
}
}
// Creates the map background and grid textures.
static bool P3MapSprite_Create(int src_sprite, int square_sprite, int x, int y, int line, int column, int blend_rate, struct page *array)
{
struct sact_sprite *src = sact_try_get_sprite(src_sprite);
struct sact_sprite *square = sact_try_get_sprite(square_sprite);
if (!src || !square)
return false;
Texture *tx_src = sprite_get_texture(src);
Texture *tx_square = sprite_get_texture(square);
int width = tx_src->w;
int height = tx_src->h;
if (p3map.bg.handle)
gfx_delete_texture(&p3map.bg);
gfx_init_texture_blank(&p3map.bg, width, height);
gfx_copy(&p3map.bg, 0, 0, tx_src, 0, 0, width, height);
if (p3map.grid.handle)
gfx_delete_texture(&p3map.grid);
gfx_init_texture_rgba(&p3map.grid, width, height, COLOR(0, 0, 0, 0));
init_grid_texture(tx_square, x, y, line, column, array);
p3map.blend_rate = blend_rate;
return true;
}
// Deletes the map background and grid textures.
static void P3MapSprite_Delete(void)
{
if (p3map.grid.handle)
gfx_delete_texture(&p3map.grid);
if (p3map.bg.handle)
gfx_delete_texture(&p3map.bg);
}
// Copies the map background and optionally blends the grid to a destination sprite.
static bool P3MapSprite_Copy(int dest_sprite, int src_x, int src_y, bool copy_square)
{
struct sact_sprite *dst = sact_get_sprite(dest_sprite);
if (!dst)
return false;
Texture *tx_dst = sprite_get_texture(dst);
int w = sprite_get_width(dst);
int h = sprite_get_height(dst);
gfx_copy(tx_dst, 0, 0, &p3map.bg, src_x, src_y, w, h);
if (copy_square)
gfx_blend_amap_alpha(tx_dst, 0, 0, &p3map.grid, src_x, src_y, w, h, p3map.blend_rate);
return true;
}
// Sets the blend rate for map rendering.
static bool P3MapSprite_CreateMatrixPixel(int blend_rate)
{
p3map.blend_rate = blend_rate;
return true;
}
// Blends the grid texture onto a destination sprite.
static bool P3MapSprite_CopyMatrix(int dest_sprite, int dest_x, int dest_y, int src_x, int src_y, int width, int height)
{
struct sact_sprite *dst = sact_get_sprite(dest_sprite);
if (!dst)
return false;
Texture *tx_dst = sprite_get_texture(dst);
gfx_blend_amap(tx_dst, dest_x, dest_y, &p3map.grid, src_x, src_y, width, height);
gfx_copy_amap_max(tx_dst, dest_x, dest_y, &p3map.grid, src_x, src_y, width, height);
return true;
}
HLL_LIBRARY(P3MapSprite,
HLL_EXPORT(Create, P3MapSprite_Create),
HLL_EXPORT(Delete, P3MapSprite_Delete),
HLL_EXPORT(Copy, P3MapSprite_Copy),
HLL_EXPORT(CreateMatrixPixel, P3MapSprite_CreateMatrixPixel),
HLL_EXPORT(CopyMatrix, P3MapSprite_CopyMatrix)
);
+69
View File
@@ -0,0 +1,69 @@
/* Copyright (C) 2026 kichikuou <KichikuouChrome@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://gnu.org/licenses/>.
*/
#include "sact.h"
#include "gfx/gfx.h"
#include "vm/page.h"
#include "hll.h"
#include "xsystem4.h"
static bool P3SquareSprite_Copy(int dest_handle, int src_handle, int size, struct page *matrix)
{
struct sact_sprite *dest = sact_try_get_sprite(dest_handle);
struct sact_sprite *src = sact_try_get_sprite(src_handle);
if (!dest || !src)
return false;
Texture *dest_tex = sprite_get_texture(dest);
Texture *src_tex = sprite_get_texture(src);
int tile_w = src_tex->w;
int tile_h = src_tex->h;
int half_w = tile_w / 2;
int half_h = tile_h / 2;
int canvas_center_x = dest_tex->w / 2;
int canvas_center_y = dest_tex->h / 2;
int side_count = size * 2 + 1;
if (matrix->nr_vars != side_count * side_count)
VM_ERROR("Expected %d variables in array, got %d", side_count * side_count, matrix->nr_vars);
int index = 0;
for (int y = -size; y <= size; ++y) {
for (int x = -size; x <= size; ++x) {
// Check if we should draw this tile
if (matrix->values[index++].i == 0)
continue;
// Isometric coordinate conversion
int screen_pos_x = canvas_center_x + (x - y - 1) * half_w;
int screen_pos_y = canvas_center_y + (x + y - 1) * half_h;
// Draw the sprite
gfx_copy_use_amap_border(dest_tex, screen_pos_x, screen_pos_y, src_tex, 0, 0, tile_w, tile_h, 1);
gfx_copy_amap_max(dest_tex, screen_pos_x, screen_pos_y, src_tex, 0, 0, tile_w, tile_h);
}
}
return true;
}
HLL_LIBRARY(P3SquareSprite,
HLL_EXPORT(Copy, P3SquareSprite_Copy)
);
+686 -5
View File
@@ -17,7 +17,10 @@
#include <assert.h>
#include "system4/ain.h"
#include "system4/string.h"
#include "vm/heap.h"
#include "vm/page.h"
#include "parts.h"
#include "hll.h"
@@ -36,6 +39,11 @@ static void PartsEngine_Update(int passed_time, bool is_skip, bool message_windo
PE_Update(passed_time, message_window_show);
}
static void PartsEngine_Update_Pascha3PC(struct string *xxx1, struct string *xxx2, int passed_time, bool is_skip, bool message_window_show)
{
PE_Update(passed_time, message_window_show);
}
// Oyako Rankan
static bool PartsEngine_AddDrawCutCGToPartsConstructionProcess_old(int parts_no,
struct string *cg_name, int dx, int dy, int sx, int sy, int w, int h,
@@ -54,6 +62,316 @@ static bool PartsEngine_AddCopyCutCGToPartsConstructionProcess_old(int parts_no,
sx, sy, w, h, 0, state);
}
// Rance 9: argument types changed from int to float
static void PartsEngine_SetComponentPos(int parts_no, float x, float y)
{
PE_SetPos(parts_no, x, y);
}
// Rance 9: return type changed from int to float
static float PartsEngine_Parts_GetPartsUpperLeftPosX(int parts_no, int state)
{
return PE_GetPartsUpperLeftPosX(parts_no, state);
}
static float PartsEngine_Parts_GetPartsUpperLeftPosY(int parts_no, int state)
{
return PE_GetPartsUpperLeftPosY(parts_no, state);
}
// Rance 9: return type changed from int to float
static float PartsEngine_Parts_GetComponentPosX(int parts_no)
{
return PE_GetPartsX(parts_no);
}
static float PartsEngine_GetComponentPosY(int parts_no)
{
return PE_GetPartsY(parts_no);
}
// Rance 9: X/Y coordinate types changed from int to float
static void PartsEngine_AddComponentMotionPos(int parts_no, float begin_x, float begin_y,
float end_x, float end_y, int begin_t, int end_t,
struct string *curve_name)
{
PE_AddMotionPos_curve(parts_no, begin_x, begin_y, end_x, end_y,
begin_t, end_t, curve_name);
}
static void PartsEngine_add_construction_process(union vm_value *ints,
union vm_value *floats, union vm_value *strings)
{
int parts_no = ints[0].i;
int state = ints[1].i;
int command = ints[2].i;
int interp_type = ints[3].i;
int sx = ints[4].i;
int sy = ints[5].i;
int sw = ints[6].i;
int sh = ints[7].i;
int dx = ints[8].i;
int dy = ints[9].i;
int dw = ints[12].i;
int dh = ints[13].i;
int r = ints[14].i;
int g = ints[15].i;
int b = ints[16].i;
int a = ints[17].i;
// int r2 = ints[18].i;
// int g2 = ints[19].i;
// int b2 = ints[20].i;
int char_space = ints[21].i;
int line_space = ints[22].i;
int font_type = ints[23].i;
int font_size = ints[24].i;
int font_r = ints[25].i;
int font_g = ints[26].i;
int font_b = ints[27].i;
int edge_r = ints[28].i;
int edge_g = ints[29].i;
int edge_b = ints[30].i;
int full_size = ints[31].i;
float bold_weight = floats[0].f;
float edge_weight = floats[1].f;
struct string *text = heap_get_string(strings[0].i);
struct string *cg_name = heap_get_string(strings[1].i);
switch (command) {
case 0: // CASConstructionProcess::SetCreate
PE_AddCreateToPartsConstructionProcess(parts_no, dw, dh, state);
break;
case 1: // CASConstructionProcess::SetCreatePixelOnly
PE_AddCreatePixelOnlyToPartsConstructionProcess(parts_no, dw, dh, state);
break;
case 2: // CASConstructionProcess::SetCreateCG
PE_AddCreateCGToProcess(parts_no, cg_name, state);
break;
case 3: // CASConstructionProcess::SetFill
PE_AddFillToPartsConstructionProcess(parts_no,
dx, dy, dw, dh, r, g, b, state);
break;
case 4: // CASConstructionProcess::SetFillAlphaColor
PE_AddFillAlphaColorToPartsConstructionProcess(parts_no,
dx, dy, dw, dh, r, g, b, a, state);
break;
case 5: // CASConstructionProcess::SetFillAMap
PE_AddFillAMapToPartsConstructionProcess(parts_no,
dx, dy, dw, dh, a, state);
break;
case 6: // CASConstructionProcess::SetFillWithAlpha
PE_AddFillWithAlphaToPartsConstructionProcess(parts_no,
dx, dy, dw, dh, r, g, b, a, state);
break;
case 7: // CASConstructionProcess::SetDrawText
PE_AddDrawTextToPartsConstructionProcess(parts_no,
dx, dy, text, font_type, font_size,
font_r, font_g, font_b, bold_weight,
edge_r, edge_g, edge_b, edge_weight,
char_space, line_space, state);
break;
case 8: // CASConstructionProcess::SetCopyText
PE_AddCopyTextToPartsConstructionProcess(parts_no,
dx, dy, text, font_type, font_size,
font_r, font_g, font_b, bold_weight,
edge_r, edge_g, edge_b, edge_weight,
char_space, line_space, state);
break;
case 9: // CASConstructionProcess::SetFillGradationHorizon
WARNING("AddConstructProcess: FillGradationHorizon unimplemented");
break;
case 10: // CASConstructionProcess::SetDrawRect
PE_AddDrawRectToPartsConstructionProcess(parts_no,
dx, dy, dw, dh, r, g, b, state);
break;
case 11: // CASConstructionProcess::SetCutCGBlend (equal scale)
PE_AddDrawCutCGToPartsConstructionProcess(parts_no, cg_name,
dx, dy, dw, dh, sx, sy, dw, dh,
interp_type, state);
break;
case 12: // CASConstructionProcess::SetCutCGCopy (equal scale)
PE_AddCopyCutCGToPartsConstructionProcess(parts_no, cg_name,
dx, dy, dw, dh, sx, sy, dw, dh,
interp_type, state);
break;
case 13: // CASConstructionProcess::SetCutCGScaleBlend
PE_AddDrawCutCGToPartsConstructionProcess(parts_no, cg_name,
dx, dy, dw, dh, sx, sy, sw, sh,
interp_type, state);
break;
case 14: // CASConstructionProcess::SetCutCGScaleCopy
PE_AddCopyCutCGToPartsConstructionProcess(parts_no, cg_name,
dx, dy, dw, dh, sx, sy, sw, sh,
interp_type, state);
break;
case 15: // CASConstructionProcess::SetGrayFilter
PE_AddGrayFilterToPartsConstructionProcess(parts_no,
dx, dy, dw, dh, full_size, state);
break;
case 16: // CASConstructionProcess::SetAddFilter
WARNING("AddConstructProcess: AddFilter unimplemented");
break;
case 17: // CASConstructionProcess::SetMulFilter
WARNING("AddConstructProcess: MulFilter unimplemented");
break;
case 18: // CASConstructionProcess::SetDrawLine
WARNING("AddConstructProcess: DrawLine unimplemented");
break;
default:
WARNING("AddConstructProcess: unknown command %d", command);
break;
}
}
// Rance9 predates the layer functions at func_id 3-5. Use the numbering from
// later PartsEngine versions as canonical and adjust Rance9's ids to match.
static bool parts_func_uses_rance9_ids;
// Generic dispatch function for PartsEngine operations.
// func_id selects the operation; arguments and return values are passed
// through three typed arrays (int/bool, float, string).
//
// Calling convention (from game script):
// 1. Push input values into the appropriate arrays.
// 2. Push a placeholder (0 or "") for each output slot.
// 3. Call PartsFunc; outputs are written back into the placeholder slots.
static int PartsEngine_PartsFunc(int func_id, struct page **array_int,
struct page **array_float, struct page **array_string)
{
int nr_ints = (array_int && *array_int) ? (*array_int)->nr_vars : 0;
union vm_value *ints = nr_ints ? (*array_int)->values : NULL;
int nr_floats = (array_float && *array_float) ? (*array_float)->nr_vars : 0;
union vm_value *floats = nr_floats ? (*array_float)->values : NULL;
int nr_strings = (array_string && *array_string) ? (*array_string)->nr_vars : 0;
union vm_value *strings = nr_strings ? (*array_string)->values : NULL;
#define REQUIRE_INTS(n) \
if (nr_ints != (n)) VM_ERROR("Invalid arguments for PartsFunc %d: expected %d ints, got %d", func_id, (n), nr_ints)
#define REQUIRE_FLOATS(n) \
if (nr_floats < (n)) VM_ERROR("Invalid arguments for PartsFunc %d: expected %d floats, got %d", func_id, (n), nr_floats)
#define REQUIRE_STRINGS(n) \
if (nr_strings < (n)) VM_ERROR("Invalid arguments for PartsFunc %d: expected %d strings, got %d", func_id, (n), nr_strings)
int canonical_func_id = func_id;
if (parts_func_uses_rance9_ids && canonical_func_id > 2)
canonical_func_id += 3;
switch (canonical_func_id) {
case 0: // void SetActiveLayer(int layer)
REQUIRE_INTS(1);
PE_set_active_controller(ints[0].i);
return 1;
case 1: // int GetActiveLayer()
REQUIRE_INTS(1);
ints[0].i = PE_get_active_controller();
return 1;
case 2: // int GetSystemOverlayLayer()
REQUIRE_INTS(1);
ints[0].i = PE_get_system_controller();
return 1;
// Layers are identified by their index in the controller stack, so the
// layer id and index conversions are identity functions.
case 3: // int GetLayerIndex(int layer_id)
case 4: // int GetLayerID(int layer_index)
REQUIRE_INTS(2);
ints[1].i = ints[0].i;
return 1;
case 5: // int NumofLayer()
REQUIRE_INTS(1);
ints[0].i = PE_get_nr_controllers();
return 1;
case 6: // void PauseMotion(bool pause)
REQUIRE_INTS(1);
PE_PauseMotion(!!ints[0].i);
return 1;
case 7: // void SetWantSave(int parts_no, bool want_save)
REQUIRE_INTS(2);
PE_parts_set_want_save(ints[0].i, !!ints[1].i);
return 1;
case 9: // bool SaveThumbnail(string filename, int reduction_factor)
REQUIRE_INTS(2);
REQUIRE_STRINGS(1);
ints[1].i = PE_save_thumbnail(heap_get_string(strings[0].i), ints[0].i);
return 1;
case 43: // float PARTS_GetAbsoluteX(int number)
REQUIRE_INTS(1);
REQUIRE_FLOATS(1);
floats[0].f = PE_parts_get_absolute_x(ints[0].i);
return 1;
case 44: // float PARTS_GetAbsoluteY(int number)
REQUIRE_INTS(1);
REQUIRE_FLOATS(1);
floats[0].f = PE_parts_get_absolute_y(ints[0].i);
return 1;
case 45: // int PARTS_GetAbsoluteZ(int number)
REQUIRE_INTS(2);
ints[1].i = PE_parts_get_absolute_z(ints[0].i);
return 1;
case 48: // void PARTS_SetLockInputState(int number, bool lock)
REQUIRE_INTS(2);
PE_parts_set_lock_input_state(ints[0].i, !!ints[1].i);
return 1;
case 60: // void AppendChild(int number, int child_number)
REQUIRE_INTS(2);
PE_SetParentPartsNumber(ints[1].i, ints[0].i);
return 1;
case 94: // void SetLayoutBoxPadding(int parts_no, int top, int bottom, int left, int right)
REQUIRE_INTS(5);
PE_set_layoutbox_padding(ints[0].i, ints[1].i, ints[2].i, ints[3].i, ints[4].i);
return 1;
case 95: // int GetLayoutBoxPaddingTop(int parts_no)
REQUIRE_INTS(2);
ints[1].i = PE_get_layoutbox_padding_top(ints[0].i);
return 1;
case 96: // int GetLayoutBoxPaddingBottom(int parts_no)
REQUIRE_INTS(2);
ints[1].i = PE_get_layoutbox_padding_bottom(ints[0].i);
return 1;
case 97: // int GetLayoutBoxPaddingLeft(int parts_no)
REQUIRE_INTS(2);
ints[1].i = PE_get_layoutbox_padding_left(ints[0].i);
return 1;
case 98: // int GetLayoutBoxPaddingRight(int parts_no)
REQUIRE_INTS(2);
ints[1].i = PE_get_layoutbox_padding_right(ints[0].i);
return 1;
case 106: // void GetPartsCGSurfaceArea(int parts_no, int *x, int *y, int *w, int *h, int state)
REQUIRE_INTS(6);
PE_GetPartsCGSurfaceArea(ints[0].i, &ints[1].i, &ints[2].i, &ints[3].i, &ints[4].i, ints[5].i);
return 1;
case 112: { // string GetText(int parts_no, int state)
REQUIRE_INTS(2);
REQUIRE_STRINGS(1);
struct string *text = PE_GetText(ints[0].i, ints[1].i);
if (text) {
heap_string_assign(strings[0].i, text);
free_string(text);
}
return 1;
}
case 162: // AddConstructProcess(ArrayInt[32], ArrayFloat[2], ArrayString[2])
REQUIRE_INTS(32);
REQUIRE_FLOATS(2);
REQUIRE_STRINGS(2);
PartsEngine_add_construction_process(ints, floats, strings);
return 1;
case 165: // bool InitPartsMovie(int parts_no, int width, int height, int bg_r, int bg_g, int bg_b, int state)
REQUIRE_INTS(8);
ints[7].i = PE_init_parts_movie(ints[0].i, ints[1].i, ints[2].i, ints[3].i, ints[4].i, ints[5].i, ints[6].i);
return 1;
case 166: // int GetMovieSprite(int parts_no, int state)
REQUIRE_INTS(3);
ints[2].i = PE_get_movie_sprite(ints[0].i, ints[1].i);
return 1;
default:
WARNING("Unknown func_id: %d", func_id);
return 0;
}
#undef REQUIRE_INTS
}
HLL_WARN_UNIMPLEMENTED(, void, PartsEngine, StopSoundWithoutSystemSound);
static void PartsEngine_PreLink(void);
HLL_LIBRARY(PartsEngine,
@@ -121,7 +439,7 @@ HLL_LIBRARY(PartsEngine,
HLL_EXPORT(StartPartsFlash, PE_StartPartsFlash),
HLL_EXPORT(GoFramePartsFlash, PE_GoFramePartsFlash),
HLL_EXPORT(GetPartsFlashEndFrame, PE_GetPartsFlashEndFrame),
HLL_TODO_EXPORT(ExistsFlashFile, PE_ExistsFlashFile),
HLL_EXPORT(ExistsFlashFile, PE_ExistsFlashFile),
HLL_EXPORT(ClearPartsConstructionProcess, PE_ClearPartsConstructionProcess),
HLL_EXPORT(AddCreateToPartsConstructionProcess, PE_AddCreateToPartsConstructionProcess),
HLL_EXPORT(AddCreatePixelOnlyToPartsConstructionProcess, PE_AddCreatePixelOnlyToPartsConstructionProcess),
@@ -129,12 +447,12 @@ HLL_LIBRARY(PartsEngine,
HLL_EXPORT(AddFillToPartsConstructionProcess, PE_AddFillToPartsConstructionProcess),
HLL_EXPORT(AddFillAlphaColorToPartsConstructionProcess, PE_AddFillAlphaColorToPartsConstructionProcess),
HLL_EXPORT(AddFillAMapToPartsConstructionProcess, PE_AddFillAMapToPartsConstructionProcess),
HLL_TODO_EXPORT(AddFillWithAlphaToPartsConstructionProcess, PartsEngine_AddFillWithAlphaToPartsConstructionProcess),
HLL_EXPORT(AddFillWithAlphaToPartsConstructionProcess, PE_AddFillWithAlphaToPartsConstructionProcess),
HLL_TODO_EXPORT(AddFillGradationHorizonToPartsConstructionProcess, PartsEngine_AddFillGradationHorizonToPartsConstructionProcess),
HLL_EXPORT(AddDrawRectToPartsConstructionProcess, PE_AddDrawRectToPartsConstructionProcess),
HLL_EXPORT(AddDrawCutCGToPartsConstructionProcess, PartsEngine_AddDrawCutCGToPartsConstructionProcess_old),
HLL_EXPORT(AddCopyCutCGToPartsConstructionProcess, PartsEngine_AddCopyCutCGToPartsConstructionProcess_old),
HLL_TODO_EXPORT(AddGrayFilterToPartsConstructionProcess, PartsEngine_AddGrayFilterToPartsConstructionProcess),
HLL_EXPORT(AddGrayFilterToPartsConstructionProcess, PE_AddGrayFilterToPartsConstructionProcess),
HLL_TODO_EXPORT(AddAddFilterToPartsConstructionProcess, PartsEngine_AddAddFilterToPartsConstructionProcess),
HLL_TODO_EXPORT(AddMulFilterToPartsConstructionProcess, PartsEngine_AddMulFilterToPartsConstructionProcess),
HLL_EXPORT(BuildPartsConstructionProcess, PE_BuildPartsConstructionProcess),
@@ -151,6 +469,7 @@ HLL_LIBRARY(PartsEngine,
HLL_EXPORT(SetPartsDrawFilter, PE_SetPartsDrawFilter),
HLL_EXPORT(SetAddColor, PE_SetAddColor),
HLL_EXPORT(SetMultiplyColor, PE_SetMultiplyColor),
HLL_EXPORT(SetPassCursor, PE_SetPassCursor),
HLL_EXPORT(SetClickable, PE_SetClickable),
HLL_EXPORT(SetSpeedupRateByMessageSkip, PE_SetSpeedupRateByMessageSkip),
HLL_TODO_EXPORT(SetResetTimerByChangeInputStatus, PartsEngine_SetResetTimerByChangeInputStatus),
@@ -161,6 +480,7 @@ HLL_LIBRARY(PartsEngine,
HLL_EXPORT(GetPartsAlpha, PE_GetPartsAlpha),
HLL_TODO_EXPORT(GetAddColor, PartsEngine_GetAddColor),
HLL_TODO_EXPORT(GetMultiplyColor, PartsEngine_GetMultiplyColor),
HLL_EXPORT(GetPartsPassCursor, PE_GetPartsPassCursor),
HLL_EXPORT(GetPartsClickable, PE_GetPartsClickable),
HLL_TODO_EXPORT(GetPartsSpeedupRateByMessageSkip, PartsEngine_GetPartsSpeedupRateByMessageSkip),
HLL_TODO_EXPORT(GetResetTimerByChangeInputStatus, PartsEngine_GetResetTimerByChangeInputStatus),
@@ -214,14 +534,366 @@ HLL_LIBRARY(PartsEngine,
HLL_EXPORT(SetPartsRotateX, PE_SetPartsRotateX),
HLL_EXPORT(SetPartsRotateY, PE_SetPartsRotateY),
HLL_EXPORT(SetPartsRotateZ, PE_SetPartsRotateZ),
HLL_TODO_EXPORT(SetPartsAlphaClipperPartsNumber, PartsEngine_SetPartsAlphaClipperPartsNumber),
HLL_EXPORT(SetPartsAlphaClipperPartsNumber, PE_SetPartsAlphaClipperPartsNumber),
HLL_EXPORT(SetPartsPixelDecide, PE_SetPartsPixelDecide),
HLL_EXPORT(IsCursorIn, PE_IsCursorIn),
HLL_EXPORT(SetThumbnailReductionSize, PE_SetThumbnailReductionSize),
HLL_EXPORT(SetThumbnailMode, PE_SetThumbnailMode),
HLL_EXPORT(Save, PE_Save),
HLL_EXPORT(SaveWithoutHideParts, PE_SaveWithoutHideParts),
HLL_EXPORT(Load, PE_Load)
HLL_EXPORT(Load, PE_Load),
// Rance 9
HLL_EXPORT(PartsFunc, PartsEngine_PartsFunc),
HLL_EXPORT(Release, PE_ReleaseParts),
HLL_TODO_EXPORT(ReleaseAll, PartsEngine_ReleaseAll),
HLL_EXPORT(ReleaseAllWithoutSystem, PE_ReleaseAllWithoutSystem),
HLL_EXPORT(GetFreeNumber, PE_GetFreeNumber),
HLL_EXPORT(IsExist, PE_IsExist),
HLL_EXPORT(AddController, PE_AddController),
HLL_EXPORT(RemoveController, PE_RemoveController),
HLL_EXPORT(UpdateComponent, PartsEngine_Update),
HLL_EXPORT(Parts_SetThumbnailReductionSize, PE_SetThumbnailReductionSize),
HLL_EXPORT(Parts_SetThumbnailMode, PE_SetThumbnailMode),
HLL_EXPORT(GetClickNumber, PE_GetClickPartsNumber),
HLL_EXPORT(StopSoundWithoutSystemSound, PartsEngine_StopSoundWithoutSystemSound),
HLL_TODO_EXPORT(ReleaseActivity, PartsEngine_ReleaseActivity),
HLL_TODO_EXPORT(CrateActivityBinary, PartsEngine_CrateActivityBinary),
HLL_TODO_EXPORT(ReadActivityBinary, PartsEngine_ReadActivityBinary),
HLL_EXPORT(ReleaseMessage, PE_ReleaseMessage),
HLL_EXPORT(PopMessage, PE_PopMessage),
HLL_EXPORT(GetMessagePartsNumber, PE_GetMessagePartsNumber),
HLL_EXPORT(GetMessageDelegateIndex, PE_GetMessageDelegateIndex),
HLL_EXPORT(GetDelegateIndex, PE_GetDelegateIndex),
HLL_EXPORT(GetMessageType, PE_GetMessageType),
HLL_EXPORT(GetMessageVariableCount, PE_GetMessageVariableCount),
HLL_EXPORT(GetMessageVariableType, PE_GetMessageVariableType),
HLL_EXPORT(GetMessageVariableInt, PE_GetMessageVariableInt),
HLL_EXPORT(GetMessageVariableFloat, PE_GetMessageVariableFloat),
HLL_EXPORT(GetMessageVariableBool, PE_GetMessageVariableBool),
HLL_EXPORT(GetMessageVariableString, PE_GetMessageVariableString),
HLL_EXPORT(SetDelegateIndex, PE_SetDelegateIndex),
HLL_TODO_EXPORT(SetFocus, PartsEngine_SetFocus),
HLL_TODO_EXPORT(IsFocus, PartsEngine_IsFocus),
HLL_EXPORT(SetComponentType, PE_SetComponentType),
HLL_EXPORT(GetComponentType, PE_GetComponentType),
HLL_EXPORT(SetComponentPos, PartsEngine_SetComponentPos),
HLL_EXPORT(SetComponentPosZ, PE_SetZ),
HLL_EXPORT(GetComponentPosX, PartsEngine_Parts_GetComponentPosX),
HLL_EXPORT(GetComponentPosY, PartsEngine_GetComponentPosY),
HLL_EXPORT(GetComponentPosZ, PE_GetPartsZ),
HLL_EXPORT(Parts_GetPartsUpperLeftPosX, PartsEngine_Parts_GetPartsUpperLeftPosX),
HLL_EXPORT(Parts_GetPartsUpperLeftPosY, PartsEngine_Parts_GetPartsUpperLeftPosY),
HLL_EXPORT(SetComponentOriginPosMode, PE_SetPartsOriginPosMode),
HLL_EXPORT(GetComponentOriginPosMode, PE_GetPartsOriginPosMode),
HLL_TODO_EXPORT(GetComponentWidth, PartsEngine_GetComponentWidth),
HLL_TODO_EXPORT(GetComponentHeight, PartsEngine_GetComponentHeight),
HLL_EXPORT(Parts_GetPartsWidth, PE_GetPartsWidth),
HLL_EXPORT(Parts_GetPartsHeight, PE_GetPartsHeight),
HLL_EXPORT(SetComponentShow, PE_SetShow),
HLL_EXPORT(IsComponentShow, PE_GetPartsShow),
HLL_EXPORT(SetComponentMessageWindowShowLink, PE_SetPartsMessageWindowShowLink),
HLL_EXPORT(IsComponentMessageWindowShowLink, PE_GetPartsMessageWindowShowLink),
HLL_EXPORT(SetComponentAlpha, PE_SetAlpha),
HLL_EXPORT(GetComponentAlpha, PE_GetPartsAlpha),
HLL_EXPORT(SetComponentAddColor, PE_SetAddColor),
HLL_TODO_EXPORT(GetComponentAddColorR, PartsEngine_GetComponentAddColorR),
HLL_TODO_EXPORT(GetComponentAddColorG, PartsEngine_GetComponentAddColorG),
HLL_TODO_EXPORT(GetComponentAddColorB, PartsEngine_GetComponentAddColorB),
HLL_EXPORT(SetComponentMulColor, PE_SetMultiplyColor),
HLL_TODO_EXPORT(GetComponentMulColorR, PartsEngine_GetComponentMulColorR),
HLL_TODO_EXPORT(GetComponentMulColorG, PartsEngine_GetComponentMulColorG),
HLL_TODO_EXPORT(GetComponentMulColorB, PartsEngine_GetComponentMulColorB),
HLL_EXPORT(SetComponentDrawFilter, PE_SetPartsDrawFilter),
HLL_TODO_EXPORT(GetComponentDrawFilter, PartsEngine_GetComponentDrawFilter),
HLL_EXPORT(SetComponentMagX, PE_SetPartsMagX),
HLL_EXPORT(SetComponentMagY, PE_SetPartsMagY),
HLL_TODO_EXPORT(GetComponentMagX, PartsEngine_GetComponentMagX),
HLL_TODO_EXPORT(GetComponentMagY, PartsEngine_GetComponentMagY),
HLL_EXPORT(SetComponentRotateX, PE_SetPartsRotateX),
HLL_EXPORT(SetComponentRotateY, PE_SetPartsRotateY),
HLL_EXPORT(SetComponentRotateZ, PE_SetPartsRotateZ),
HLL_TODO_EXPORT(GetComponentRotateX, PartsEngine_GetComponentRotateX),
HLL_TODO_EXPORT(GetComponentRotateY, PartsEngine_GetComponentRotateY),
HLL_EXPORT(GetComponentRotateZ, PE_GetPartsRotateZ),
HLL_EXPORT(SetComponentMargin, PE_SetComponentMargin),
HLL_EXPORT(GetComponentMarginTop, PE_GetComponentMarginTop),
HLL_EXPORT(GetComponentMarginBottom, PE_GetComponentMarginBottom),
HLL_EXPORT(GetComponentMarginLeft, PE_GetComponentMarginLeft),
HLL_EXPORT(GetComponentMarginRight, PE_GetComponentMarginRight),
HLL_EXPORT(SetComponentAlphaClipper, PE_SetPartsAlphaClipperPartsNumber),
HLL_TODO_EXPORT(GetComponentAlphaClipper, PartsEngine_GetComponentAlphaClipper),
HLL_TODO_EXPORT(SetComponentTextureFilterType, PartsEngine_SetComponentTextureFilterType),
HLL_TODO_EXPORT(GetComponentTextureFilterType, PartsEngine_GetComponentTextureFilterType),
HLL_TODO_EXPORT(SetComponentMipmap, PartsEngine_SetComponentMipmap),
HLL_TODO_EXPORT(IsComponentMipmap, PartsEngine_IsComponentMipmap),
HLL_EXPORT(SetComponentSpeedupRateByMessageSkip, PE_SetSpeedupRateByMessageSkip),
HLL_EXPORT(GetComponentSpeedupRateByMessageSkip, PE_GetSpeedupRateByMessageSkip),
HLL_EXPORT(AddComponentMotionPos, PartsEngine_AddComponentMotionPos),
HLL_EXPORT(AddComponentMotionAlpha, PE_AddMotionAlpha_curve),
HLL_TODO_EXPORT(AddComponentMotionCG, PartsEngine_AddComponentMotionCG),
HLL_TODO_EXPORT(AddComponentMotionCGTermination, PartsEngine_AddComponentMotionCGTermination),
HLL_EXPORT(AddComponentMotionHGaugeRate, PE_AddMotionHGaugeRate_curve),
HLL_EXPORT(AddComponentMotionVGaugeRate, PE_AddMotionVGaugeRate_curve),
HLL_EXPORT(AddComponentMotionNumeralNumber, PE_AddMotionNumeralNumber_curve),
HLL_EXPORT(AddComponentMotionMagX, PE_AddMotionMagX_curve),
HLL_EXPORT(AddComponentMotionMagY, PE_AddMotionMagY_curve),
HLL_EXPORT(AddComponentMotionRotateX, PE_AddMotionRotateX_curve),
HLL_EXPORT(AddComponentMotionRotateY, PE_AddMotionRotateY_curve),
HLL_EXPORT(AddComponentMotionRotateZ, PE_AddMotionRotateZ_curve),
HLL_EXPORT(AddComponentMotionVibrationSize, PE_AddMotionVibrationSize),
HLL_TODO_EXPORT(SuspendBuildView, PartsEngine_SuspendBuildView),
HLL_TODO_EXPORT(SuspendBuildViewAt, PartsEngine_SuspendBuildViewAt),
HLL_TODO_EXPORT(ResumeBuildView, PartsEngine_ResumeBuildView),
HLL_TODO_EXPORT(SetButtonSize, PartsEngine_SetButtonSize),
HLL_TODO_EXPORT(SetButtonDrag, PartsEngine_SetButtonDrag),
HLL_TODO_EXPORT(IsButtonDrag, PartsEngine_IsButtonDrag),
HLL_TODO_EXPORT(SetButtonEnable, PartsEngine_SetButtonEnable),
HLL_TODO_EXPORT(IsButtonEnable, PartsEngine_IsButtonEnable),
HLL_TODO_EXPORT(SetButtonPixelDecide, PartsEngine_SetButtonPixelDecide),
HLL_TODO_EXPORT(IsButtonPixelDecide, PartsEngine_IsButtonPixelDecide),
HLL_TODO_EXPORT(SetButtonColor, PartsEngine_SetButtonColor),
HLL_TODO_EXPORT(GetButtonR, PartsEngine_GetButtonR),
HLL_TODO_EXPORT(GetButtonG, PartsEngine_GetButtonG),
HLL_TODO_EXPORT(GetButtonB, PartsEngine_GetButtonB),
HLL_TODO_EXPORT(SetButtonFontProperty, PartsEngine_SetButtonFontProperty),
HLL_TODO_EXPORT(GetButtonFontProperty, PartsEngine_GetButtonFontProperty),
HLL_TODO_EXPORT(SetButtonOnCursorSoundNumber, PartsEngine_SetButtonOnCursorSoundNumber),
HLL_TODO_EXPORT(SetButtonClickSoundNumber, PartsEngine_SetButtonClickSoundNumber),
HLL_TODO_EXPORT(GetButtonOnCursorSoundNumber, PartsEngine_GetButtonOnCursorSoundNumber),
HLL_TODO_EXPORT(GetButtonClickSoundNumber, PartsEngine_GetButtonClickSoundNumber),
HLL_TODO_EXPORT(SetButtonCGName, PartsEngine_SetButtonCGName),
HLL_TODO_EXPORT(GetButtonCGName, PartsEngine_GetButtonCGName),
HLL_TODO_EXPORT(SetButtonText, PartsEngine_SetButtonText),
HLL_TODO_EXPORT(GetButtonText, PartsEngine_GetButtonText),
HLL_TODO_EXPORT(SetCheckBoxSize, PartsEngine_SetCheckBoxSize),
HLL_TODO_EXPORT(SetCheckBoxDrag, PartsEngine_SetCheckBoxDrag),
HLL_TODO_EXPORT(IsCheckBoxDrag, PartsEngine_IsCheckBoxDrag),
HLL_TODO_EXPORT(CheckBoxChecked, PartsEngine_CheckBoxChecked),
HLL_TODO_EXPORT(IsCheckBoxChecked, PartsEngine_IsCheckBoxChecked),
HLL_TODO_EXPORT(SetCheckBoxColor, PartsEngine_SetCheckBoxColor),
HLL_TODO_EXPORT(GetCheckBoxR, PartsEngine_GetCheckBoxR),
HLL_TODO_EXPORT(GetCheckBoxG, PartsEngine_GetCheckBoxG),
HLL_TODO_EXPORT(GetCheckBoxB, PartsEngine_GetCheckBoxB),
HLL_TODO_EXPORT(SetCheckBoxFontProperty, PartsEngine_SetCheckBoxFontProperty),
HLL_TODO_EXPORT(GetCheckBoxFontProperty, PartsEngine_GetCheckBoxFontProperty),
HLL_TODO_EXPORT(SetCheckBoxOnCursorSoundNumber, PartsEngine_SetCheckBoxOnCursorSoundNumber),
HLL_TODO_EXPORT(SetCheckBoxClickSoundNumber, PartsEngine_SetCheckBoxClickSoundNumber),
HLL_TODO_EXPORT(GetCheckBoxOnCursorSoundNumber, PartsEngine_GetCheckBoxOnCursorSoundNumber),
HLL_TODO_EXPORT(GetCheckBoxClickSoundNumber, PartsEngine_GetCheckBoxClickSoundNumber),
HLL_TODO_EXPORT(SetCheckBoxCGName, PartsEngine_SetCheckBoxCGName),
HLL_TODO_EXPORT(GetCheckBoxCGName, PartsEngine_GetCheckBoxCGName),
HLL_TODO_EXPORT(SetCheckBoxText, PartsEngine_SetCheckBoxText),
HLL_TODO_EXPORT(GetCheckBoxText, PartsEngine_GetCheckBoxText),
HLL_TODO_EXPORT(SetVScrollbarOnCursorSoundNumber, PartsEngine_SetVScrollbarOnCursorSoundNumber),
HLL_TODO_EXPORT(SetVScrollbarClickSoundNumber, PartsEngine_SetVScrollbarClickSoundNumber),
HLL_TODO_EXPORT(GetVScrollbarOnCursorSoundNumber, PartsEngine_GetVScrollbarOnCursorSoundNumber),
HLL_TODO_EXPORT(GetVScrollbarClickSoundNumber, PartsEngine_GetVScrollbarClickSoundNumber),
HLL_TODO_EXPORT(SetVScrollbarSize, PartsEngine_SetVScrollbarSize),
HLL_TODO_EXPORT(SetVScrollbarUpHeight, PartsEngine_SetVScrollbarUpHeight),
HLL_TODO_EXPORT(SetVScrollbarDownHeight, PartsEngine_SetVScrollbarDownHeight),
HLL_TODO_EXPORT(GetVScrollbarUpHeight, PartsEngine_GetVScrollbarUpHeight),
HLL_TODO_EXPORT(GetVScrollbarDownHeight, PartsEngine_GetVScrollbarDownHeight),
HLL_TODO_EXPORT(SetVScrollbarTotalSize, PartsEngine_SetVScrollbarTotalSize),
HLL_TODO_EXPORT(SetVScrollbarViewSize, PartsEngine_SetVScrollbarViewSize),
HLL_TODO_EXPORT(SetVScrollbarScrollPos, PartsEngine_SetVScrollbarScrollPos),
HLL_TODO_EXPORT(SetVScrollbarScrollRate, PartsEngine_SetVScrollbarScrollRate),
HLL_TODO_EXPORT(SetVScrollbarMoveSizeByButton, PartsEngine_SetVScrollbarMoveSizeByButton),
HLL_TODO_EXPORT(GetVScrollbarTotalSize, PartsEngine_GetVScrollbarTotalSize),
HLL_TODO_EXPORT(GetVScrollbarViewSize, PartsEngine_GetVScrollbarViewSize),
HLL_TODO_EXPORT(GetVScrollbarScrollPos, PartsEngine_GetVScrollbarScrollPos),
HLL_TODO_EXPORT(GetVScrollbarScrollRate, PartsEngine_GetVScrollbarScrollRate),
HLL_TODO_EXPORT(GetVScrollbarMoveSizeByButton, PartsEngine_GetVScrollbarMoveSizeByButton),
HLL_TODO_EXPORT(SetVScrollbarCGName, PartsEngine_SetVScrollbarCGName),
HLL_TODO_EXPORT(GetVScrollbarCGName, PartsEngine_GetVScrollbarCGName),
HLL_TODO_EXPORT(SetHScrollbarOnCursorSoundNumber, PartsEngine_SetHScrollbarOnCursorSoundNumber),
HLL_TODO_EXPORT(SetHScrollbarClickSoundNumber, PartsEngine_SetHScrollbarClickSoundNumber),
HLL_TODO_EXPORT(GetHScrollbarOnCursorSoundNumber, PartsEngine_GetHScrollbarOnCursorSoundNumber),
HLL_TODO_EXPORT(GetHScrollbarClickSoundNumber, PartsEngine_GetHScrollbarClickSoundNumber),
HLL_TODO_EXPORT(SetHScrollbarSize, PartsEngine_SetHScrollbarSize),
HLL_TODO_EXPORT(SetHScrollbarLeftWidth, PartsEngine_SetHScrollbarLeftWidth),
HLL_TODO_EXPORT(SetHScrollbarRightWidth, PartsEngine_SetHScrollbarRightWidth),
HLL_TODO_EXPORT(GetHScrollbarLeftWidth, PartsEngine_GetHScrollbarLeftWidth),
HLL_TODO_EXPORT(GetHScrollbarRightWidth, PartsEngine_GetHScrollbarRightWidth),
HLL_TODO_EXPORT(SetHScrollbarTotalSize, PartsEngine_SetHScrollbarTotalSize),
HLL_TODO_EXPORT(SetHScrollbarViewSize, PartsEngine_SetHScrollbarViewSize),
HLL_TODO_EXPORT(SetHScrollbarScrollPos, PartsEngine_SetHScrollbarScrollPos),
HLL_TODO_EXPORT(SetHScrollbarScrollRate, PartsEngine_SetHScrollbarScrollRate),
HLL_TODO_EXPORT(SetHScrollbarMoveSizeByButton, PartsEngine_SetHScrollbarMoveSizeByButton),
HLL_TODO_EXPORT(GetHScrollbarTotalSize, PartsEngine_GetHScrollbarTotalSize),
HLL_TODO_EXPORT(GetHScrollbarViewSize, PartsEngine_GetHScrollbarViewSize),
HLL_TODO_EXPORT(GetHScrollbarScrollPos, PartsEngine_GetHScrollbarScrollPos),
HLL_TODO_EXPORT(GetHScrollbarScrollRate, PartsEngine_GetHScrollbarScrollRate),
HLL_TODO_EXPORT(GetHScrollbarMoveSizeByButton, PartsEngine_GetHScrollbarMoveSizeByButton),
HLL_TODO_EXPORT(SetHScrollbarCGName, PartsEngine_SetHScrollbarCGName),
HLL_TODO_EXPORT(GetHScrollbarCGName, PartsEngine_GetHScrollbarCGName),
HLL_TODO_EXPORT(SetTextBoxSize, PartsEngine_SetTextBoxSize),
HLL_TODO_EXPORT(SetTextBoxFontProperty, PartsEngine_SetTextBoxFontProperty),
HLL_TODO_EXPORT(GetTextBoxFontProperty, PartsEngine_GetTextBoxFontProperty),
HLL_TODO_EXPORT(SetTextBoxText, PartsEngine_SetTextBoxText),
HLL_TODO_EXPORT(GetTextBoxText, PartsEngine_GetTextBoxText),
HLL_TODO_EXPORT(SetTextBoxMaxTextLength, PartsEngine_SetTextBoxMaxTextLength),
HLL_TODO_EXPORT(GetTextBoxMaxTextLength, PartsEngine_GetTextBoxMaxTextLength),
HLL_TODO_EXPORT(SetTextBoxSelectColor, PartsEngine_SetTextBoxSelectColor),
HLL_TODO_EXPORT(GetTextBoxSelectR, PartsEngine_GetTextBoxSelectR),
HLL_TODO_EXPORT(GetTextBoxSelectG, PartsEngine_GetTextBoxSelectG),
HLL_TODO_EXPORT(GetTextBoxSelectB, PartsEngine_GetTextBoxSelectB),
HLL_TODO_EXPORT(SetTextBoxCGName, PartsEngine_SetTextBoxCGName),
HLL_TODO_EXPORT(GetTextBoxCGName, PartsEngine_GetTextBoxCGName),
HLL_TODO_EXPORT(OpenTextBoxIME, PartsEngine_OpenTextBoxIME),
HLL_TODO_EXPORT(CloseTextBoxIME, PartsEngine_CloseTextBoxIME),
HLL_TODO_EXPORT(SetListBoxSize, PartsEngine_SetListBoxSize),
HLL_TODO_EXPORT(SetListBoxLineHeight, PartsEngine_SetListBoxLineHeight),
HLL_TODO_EXPORT(GetListBoxLineHeight, PartsEngine_GetListBoxLineHeight),
HLL_TODO_EXPORT(SetListBoxMargin, PartsEngine_SetListBoxMargin),
HLL_TODO_EXPORT(GetListBoxWidthMargin, PartsEngine_GetListBoxWidthMargin),
HLL_TODO_EXPORT(GetListBoxHeightMargin, PartsEngine_GetListBoxHeightMargin),
HLL_TODO_EXPORT(SetListBoxCGName, PartsEngine_SetListBoxCGName),
HLL_TODO_EXPORT(GetListBoxCGName, PartsEngine_GetListBoxCGName),
HLL_TODO_EXPORT(SetListBoxScrollPos, PartsEngine_SetListBoxScrollPos),
HLL_TODO_EXPORT(GetListBoxScrollPos, PartsEngine_GetListBoxScrollPos),
HLL_TODO_EXPORT(AddListBoxItem, PartsEngine_AddListBoxItem),
HLL_TODO_EXPORT(InsertListBoxItem, PartsEngine_InsertListBoxItem),
HLL_TODO_EXPORT(SetListBoxItem, PartsEngine_SetListBoxItem),
HLL_TODO_EXPORT(GetListBoxItemCount, PartsEngine_GetListBoxItemCount),
HLL_TODO_EXPORT(GetListBoxItem, PartsEngine_GetListBoxItem),
HLL_TODO_EXPORT(EraseListBoxItem, PartsEngine_EraseListBoxItem),
HLL_TODO_EXPORT(ClearListBoxItem, PartsEngine_ClearListBoxItem),
HLL_TODO_EXPORT(GetListBoxOnCursorItemIndex, PartsEngine_GetListBoxOnCursorItemIndex),
HLL_TODO_EXPORT(GetListBoxOnCursorItem, PartsEngine_GetListBoxOnCursorItem),
HLL_TODO_EXPORT(SetListBoxFontProperty, PartsEngine_SetListBoxFontProperty),
HLL_TODO_EXPORT(GetListBoxFontProperty, PartsEngine_GetListBoxFontProperty),
HLL_TODO_EXPORT(SetListBoxSelectIndex, PartsEngine_SetListBoxSelectIndex),
HLL_TODO_EXPORT(GetListBoxSelectIndex, PartsEngine_GetListBoxSelectIndex),
HLL_TODO_EXPORT(SetComboBoxSize, PartsEngine_SetComboBoxSize),
HLL_TODO_EXPORT(SetComboBoxTextMargin, PartsEngine_SetComboBoxTextMargin),
HLL_TODO_EXPORT(GetComboBoxTextWidthMargin, PartsEngine_GetComboBoxTextWidthMargin),
HLL_TODO_EXPORT(GetComboBoxTextHeightMargin, PartsEngine_GetComboBoxTextHeightMargin),
HLL_TODO_EXPORT(SetComboBoxCGName, PartsEngine_SetComboBoxCGName),
HLL_TODO_EXPORT(GetComboBoxCGName, PartsEngine_GetComboBoxCGName),
HLL_TODO_EXPORT(SetComboBoxText, PartsEngine_SetComboBoxText),
HLL_TODO_EXPORT(GetComboBoxText, PartsEngine_GetComboBoxText),
HLL_TODO_EXPORT(SetComboBoxFontProperty, PartsEngine_SetComboBoxFontProperty),
HLL_TODO_EXPORT(GetComboBoxFontProperty, PartsEngine_GetComboBoxFontProperty),
HLL_TODO_EXPORT(SetMultiTextBoxSize, PartsEngine_SetMultiTextBoxSize),
HLL_TODO_EXPORT(SetMultiTextBoxFontProperty, PartsEngine_SetMultiTextBoxFontProperty),
HLL_TODO_EXPORT(GetMultiTextBoxFontProperty, PartsEngine_GetMultiTextBoxFontProperty),
HLL_TODO_EXPORT(SetMultiTextBoxText, PartsEngine_SetMultiTextBoxText),
HLL_TODO_EXPORT(GetMultiTextBoxText, PartsEngine_GetMultiTextBoxText),
HLL_TODO_EXPORT(SetMultiTextBoxMaxTextLength, PartsEngine_SetMultiTextBoxMaxTextLength),
HLL_TODO_EXPORT(GetMultiTextBoxMaxTextLength, PartsEngine_GetMultiTextBoxMaxTextLength),
HLL_TODO_EXPORT(SetMultiTextBoxSelectColor, PartsEngine_SetMultiTextBoxSelectColor),
HLL_TODO_EXPORT(GetMultiTextBoxSelectR, PartsEngine_GetMultiTextBoxSelectR),
HLL_TODO_EXPORT(GetMultiTextBoxSelectG, PartsEngine_GetMultiTextBoxSelectG),
HLL_TODO_EXPORT(GetMultiTextBoxSelectB, PartsEngine_GetMultiTextBoxSelectB),
HLL_TODO_EXPORT(SetMultiTextBoxCGName, PartsEngine_SetMultiTextBoxCGName),
HLL_TODO_EXPORT(GetMultiTextBoxCGName, PartsEngine_GetMultiTextBoxCGName),
HLL_EXPORT(SetLayoutBoxLayoutType, PE_SetLayoutBoxLayoutType),
HLL_EXPORT(GetLayoutBoxLayoutType, PE_GetLayoutBoxLayoutType),
HLL_EXPORT(SetLayoutBoxReturn, PE_SetLayoutBoxReturn),
HLL_EXPORT(IsLayoutBoxReturn, PE_IsLayoutBoxReturn),
HLL_EXPORT(GetLayoutBoxReturnSize, PE_GetLayoutBoxReturnSize),
HLL_EXPORT(SetLayoutBoxAlign, PE_SetLayoutBoxAlign),
HLL_EXPORT(GetLayoutBoxAlign, PE_GetLayoutBoxAlign),
HLL_EXPORT(Parts_SetPartsCG, PE_SetPartsCG),
HLL_EXPORT(Parts_GetPartsCGName, PE_GetPartsCGName),
HLL_EXPORT(Parts_SetPartsCGSurfaceArea, PE_SetPartsCGSurfaceArea),
HLL_EXPORT(Parts_SetLoopCG, PE_SetLoopCG),
HLL_EXPORT(Parts_SetLoopCGSurfaceArea, PE_SetLoopCGSurfaceArea),
HLL_EXPORT(Parts_SetText, PE_SetText),
HLL_EXPORT(Parts_AddPartsText, PE_AddPartsText),
HLL_TODO_EXPORT(Parts_DeletePartsTopTextLine, PartsEngine_Parts_DeletePartsTopTextLine),
HLL_EXPORT(Parts_SetPartsTextSurfaceArea, PE_SetPartsTextSurfaceArea),
HLL_TODO_EXPORT(Parts_SetPartsTextHighlight, PartsEngine_Parts_SetPartsTextHighlight),
HLL_TODO_EXPORT(Parts_AddPartsTextHighlight, PartsEngine_Parts_AddPartsTextHighlight),
HLL_TODO_EXPORT(Parts_ClearPartsTextHighlight, PartsEngine_Parts_ClearPartsTextHighlight),
HLL_TODO_EXPORT(Parts_SetPartsTextCountReturn, PartsEngine_Parts_SetPartsTextCountReturn),
HLL_TODO_EXPORT(Parts_GetPartsTextCountReturn, PartsEngine_Parts_GetPartsTextCountReturn),
HLL_EXPORT(Parts_SetFont, PE_SetFont),
HLL_EXPORT(Parts_SetPartsFontType, PE_SetPartsFontType),
HLL_EXPORT(Parts_SetPartsFontSize, PE_SetPartsFontSize),
HLL_EXPORT(Parts_SetPartsFontColor, PE_SetPartsFontColor),
HLL_EXPORT(Parts_SetPartsFontBoldWeight, PE_SetPartsFontBoldWeight),
HLL_EXPORT(Parts_SetPartsFontEdgeColor, PE_SetPartsFontEdgeColor),
HLL_EXPORT(Parts_SetPartsFontEdgeWeight, PE_SetPartsFontEdgeWeight),
HLL_EXPORT(Parts_SetTextCharSpace, PE_SetTextCharSpace),
HLL_EXPORT(Parts_SetTextLineSpace, PE_SetTextLineSpace),
HLL_EXPORT(Parts_SetHGaugeCG, PE_SetHGaugeCG),
HLL_EXPORT(Parts_SetHGaugeRate, PE_SetHGaugeRate),
HLL_EXPORT(Parts_SetVGaugeCG, PE_SetVGaugeCG),
HLL_EXPORT(Parts_SetVGaugeRate, PE_SetVGaugeRate),
HLL_EXPORT(Parts_SetHGaugeSurfaceArea, PE_SetHGaugeSurfaceArea),
HLL_EXPORT(Parts_SetVGaugeSurfaceArea, PE_SetVGaugeSurfaceArea),
HLL_EXPORT(Parts_SetNumeralCG, PE_SetNumeralCG),
HLL_EXPORT(Parts_SetNumeralLinkedCGNumberWidthWidthList, PE_SetNumeralLinkedCGNumberWidthWidthList),
HLL_TODO_EXPORT(Parts_SetNumeralFont, PartsEngine_Parts_SetNumeralFont),
HLL_EXPORT(Parts_SetNumeralNumber, PE_SetNumeralNumber),
HLL_EXPORT(Parts_SetNumeralShowComma, PE_SetNumeralShowComma),
HLL_EXPORT(Parts_SetNumeralSpace, PE_SetNumeralSpace),
HLL_EXPORT(Parts_SetNumeralLength, PE_SetNumeralLength),
HLL_EXPORT(Parts_SetNumeralSurfaceArea, PE_SetNumeralSurfaceArea),
HLL_EXPORT(Parts_SetPartsRectangleDetectionSize, PE_SetPartsRectangleDetectionSize),
HLL_TODO_EXPORT(Parts_SetPartsRectangleDetectionSurfaceArea, PartsEngine_Parts_SetPartsRectangleDetectionSurfaceArea),
HLL_EXPORT(Parts_SetPartsCGDetectionSize, PE_SetPartsCGDetectionSize),
HLL_TODO_EXPORT(Parts_SetPartsCGDetectionSurfaceArea, PartsEngine_Parts_SetPartsCGDetectionSurfaceArea),
HLL_EXPORT(Parts_SetPartsFlat, PE_SetPartsFlat),
HLL_EXPORT(Parts_IsPartsFlatEnd, PE_IsPartsFlatEnd),
HLL_EXPORT(Parts_GetPartsFlatCurrentFrameNumber, PE_GetPartsFlatCurrentFrameNumber),
HLL_EXPORT(Parts_BackPartsFlatBeginFrame, PE_BackPartsFlatBeginFrame),
HLL_EXPORT(Parts_StepPartsFlatFinalFrame, PE_StepPartsFlatFinalFrame),
HLL_EXPORT(Parts_SetPartsFlatSurfaceArea, PE_SetPartsFlatSurfaceArea),
HLL_EXPORT(Parts_SetPartsFlatAndStop, PE_SetPartsFlatAndStop),
HLL_EXPORT(Parts_StopPartsFlat, PE_StopPartsFlat),
HLL_EXPORT(Parts_StartPartsFlat, PE_StartPartsFlat),
HLL_EXPORT(Parts_GoFramePartsFlat, PE_GoFramePartsFlat),
HLL_EXPORT(Parts_GetPartsFlatEndFrame, PE_GetPartsFlatEndFrame),
HLL_EXPORT(Parts_ExistsFlatFile, PE_ExistsFlatFile),
HLL_TODO_EXPORT(Parts_GetPartsFlatDataInfo, PartsEngine_Parts_GetPartsFlatDataInfo),
HLL_TODO_EXPORT(Parts_ChangeFlatCG, PartsEngine_Parts_ChangeFlatCG),
HLL_TODO_EXPORT(Parts_ChangeFlatSound, PartsEngine_Parts_ChangeFlatSound),
HLL_EXPORT(Parts_ClearPartsConstructionProcess, PE_ClearPartsConstructionProcess),
HLL_EXPORT(Parts_AddCreateToPartsConstructionProcess, PE_AddCreateToPartsConstructionProcess),
HLL_EXPORT(Parts_AddCreatePixelOnlyToPartsConstructionProcess, PE_AddCreatePixelOnlyToPartsConstructionProcess),
HLL_EXPORT(Parts_AddCreateCGToProcess, PE_AddCreateCGToProcess),
HLL_EXPORT(Parts_AddFillToPartsConstructionProcess, PE_AddFillToPartsConstructionProcess),
HLL_EXPORT(Parts_AddFillAlphaColorToPartsConstructionProcess, PE_AddFillAlphaColorToPartsConstructionProcess),
HLL_EXPORT(Parts_AddFillAMapToPartsConstructionProcess, PE_AddFillAMapToPartsConstructionProcess),
HLL_EXPORT(Parts_AddFillWithAlphaToPartsConstructionProcess, PE_AddFillWithAlphaToPartsConstructionProcess),
HLL_TODO_EXPORT(Parts_AddFillGradationHorizonToPartsConstructionProcess, PartsEngine_Parts_AddFillGradationHorizonToPartsConstructionProcess),
HLL_EXPORT(Parts_AddDrawRectToPartsConstructionProcess, PE_AddDrawRectToPartsConstructionProcess),
HLL_EXPORT(Parts_AddDrawCutCGToPartsConstructionProcess, PE_AddDrawCutCGToPartsConstructionProcess),
HLL_EXPORT(Parts_AddCopyCutCGToPartsConstructionProcess, PE_AddCopyCutCGToPartsConstructionProcess),
HLL_EXPORT(Parts_AddGrayFilterToPartsConstructionProcess, PE_AddGrayFilterToPartsConstructionProcess),
HLL_TODO_EXPORT(Parts_AddAddFilterToPartsConstructionProcess, PartsEngine_Parts_AddAddFilterToPartsConstructionProcess),
HLL_TODO_EXPORT(Parts_AddMulFilterToPartsConstructionProcess, PartsEngine_Parts_AddMulFilterToPartsConstructionProcess),
HLL_TODO_EXPORT(Parts_AddDrawLineToPartsConstructionProcess, PartsEngine_Parts_AddDrawLineToPartsConstructionProcess),
HLL_EXPORT(Parts_BuildPartsConstructionProcess, PE_BuildPartsConstructionProcess),
HLL_EXPORT(Parts_AddDrawTextToPartsConstructionProcess, PE_AddDrawTextToPartsConstructionProcess),
HLL_EXPORT(Parts_AddCopyTextToPartsConstructionProcess, PE_AddCopyTextToPartsConstructionProcess),
HLL_EXPORT(Parts_SetPartsConstructionSurfaceArea, PE_SetPartsConstructionSurfaceArea),
HLL_EXPORT(Parts_CreateParts3DLayerPluginID, PE_CreateParts3DLayerPluginID),
HLL_EXPORT(Parts_GetParts3DLayerPluginID, PE_GetParts3DLayerPluginID),
HLL_EXPORT(Parts_ReleaseParts3DLayerPluginID, PE_ReleaseParts3DLayerPluginID),
HLL_EXPORT(Parts_SetPassCursor, PE_SetPassCursor),
HLL_EXPORT(Parts_SetClickable, PE_SetClickable),
HLL_TODO_EXPORT(Parts_SetResetTimerByChangeInputStatus, PartsEngine_Parts_SetResetTimerByChangeInputStatus),
HLL_EXPORT(Parts_SetDrag, PE_SetDrag),
HLL_EXPORT(Parts_SetParentPartsNumber, PE_SetParentPartsNumber),
HLL_EXPORT(Parts_SetInputState, PE_SetInputState),
HLL_EXPORT(Parts_SetOnCursorShowLinkPartsNumber, PE_SetOnCursorShowLinkPartsNumber),
HLL_TODO_EXPORT(Parts_SetSoundNumber, PartsEngine_Parts_SetSoundNumber),
HLL_EXPORT(Parts_SetPartsPixelDecide, PE_SetPartsPixelDecide),
HLL_EXPORT(Parts_GetPartsPassCursor, PE_GetPartsPassCursor),
HLL_EXPORT(Parts_GetPartsClickable, PE_GetPartsClickable),
HLL_TODO_EXPORT(Parts_GetResetTimerByChangeInputStatus, PartsEngine_Parts_GetResetTimerByChangeInputStatus),
HLL_TODO_EXPORT(Parts_GetPartsDrag, PartsEngine_Parts_GetPartsDrag),
HLL_TODO_EXPORT(Parts_GetParentPartsNumber, PartsEngine_Parts_GetParentPartsNumber),
HLL_EXPORT(Parts_GetInputState, PE_GetInputState),
HLL_TODO_EXPORT(Parts_GetOnCursorShowLinkPartsNumber, PartsEngine_Parts_GetOnCursorShowLinkPartsNumber),
HLL_TODO_EXPORT(Parts_GetSoundNumber, PartsEngine_Parts_GetSoundNumber),
HLL_TODO_EXPORT(Parts_IsPartsPixelDecide, PartsEngine_Parts_IsPartsPixelDecide),
HLL_EXPORT(Parts_IsCursorIn, PE_IsCursorIn),
HLL_TODO_EXPORT(SaveParts, PartsEngine_SaveParts),
HLL_TODO_EXPORT(LoadParts, PartsEngine_LoadParts)
);
static struct ain_hll_function *get_fun(int libno, const char *name)
@@ -246,4 +918,13 @@ static void PartsEngine_PreLink(void)
static_library_replace(&lib_PartsEngine, "AddCopyCutCGToPartsConstructionProcess",
PE_AddCopyCutCGToPartsConstructionProcess);
}
fun = get_fun(libno, "Update");
if (fun && fun->nr_arguments == 5) {
static_library_replace(&lib_PartsEngine, "Update",
PartsEngine_Update_Pascha3PC);
}
if (get_fun(libno, "AddController")) {
PE_enable_multi_controller();
}
parts_func_uses_rance9_ids = ain_get_function(ain, "PARTS_GetLayerIndex") < 0;
}
+7 -7
View File
@@ -18,7 +18,6 @@
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <zlib.h>
#include "cJSON.h"
#include "system4.h"
@@ -26,6 +25,7 @@
#include "system4/little_endian.h"
#include "system4/string.h"
#include "system4/file.h"
#include "system4/zlib.h"
#include "hll.h"
#include "xsystem4.h"
@@ -97,10 +97,11 @@ static void write_register(unsigned handle)
}
size_t raw_size = buf.index;
unsigned long compressed_size = compressBound(raw_size);
size_t compressed_size = zlib_compress_bound(raw_size);
uint8_t *compressed = xmalloc(compressed_size);
int r = compress2(compressed, &compressed_size, buf.buf, raw_size, Z_BEST_COMPRESSION);
if (r != Z_OK) {
compressed_size = zlib_compress(compressed, compressed_size, buf.buf,
raw_size, ZLIB_BEST_COMPRESSION);
if (!compressed_size) {
WARNING("Failed to compress PassRegister file: %s", display_utf0(reg->filename));
goto end;
}
@@ -136,15 +137,14 @@ static void read_register_psr(struct passregister *reg, uint8_t *data, size_t da
WARNING("Unexpected PassRegister version %d", version);
return;
}
unsigned long raw_size = LittleEndian_getDW(data, 8);
size_t raw_size = LittleEndian_getDW(data, 8);
size_t compressed_size = LittleEndian_getDW(data, 12);
if (PSR_HEADER_SIZE + compressed_size != data_len) {
WARNING("Broken PassRegister file");
return;
}
uint8_t *raw = xmalloc(raw_size);
int r = uncompress(raw, &raw_size, data + PSR_HEADER_SIZE, compressed_size);
if (r != Z_OK) {
if (!zlib_decompress_exact(raw, raw_size, data + PSR_HEADER_SIZE, compressed_size)) {
WARNING("PassRegister: failed to uncompress");
free(raw);
return;
+542 -111
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -1031,6 +1031,7 @@ int sact_TRANS_Begin(int type)
int sact_TRANS_Update(float rate)
{
sprite_call_plugins();
if (scene_is_dirty)
scene_render();
return effect_update(rate);
+101
View File
@@ -43,6 +43,105 @@ struct sr_text_properties {
struct text_style ts;
};
// A font_size that uses FNL first and Gothic for missing glyphs.
struct sr_fallback_font_size {
struct font_size super;
struct font_size *fnl;
struct font_size *gothic;
struct sr_fallback_font_size *next;
};
#define SR_FALLBACK_FONT_SCALE 0.75f
#define SR_FALLBACK_VERTICAL_SHIFT_RATIO 0.65f
static struct sr_fallback_font_size *sr_fallback_sizes;
static uint32_t sjis_code_to_unicode(uint32_t code)
{
char sjis[] = { code >> 8, code, 0 };
const char *p = code > 0xff ? sjis : sjis + 1;
int unicode;
sjis_char2unicode(p, &unicode);
return unicode;
}
static bool sr_fallback_get_glyph(struct font_size *_size, struct glyph *glyph,
uint32_t code, enum font_weight weight)
{
struct sr_fallback_font_size *size = (struct sr_fallback_font_size*)_size;
if (size->fnl->font->get_glyph(size->fnl, glyph, code, weight))
return true;
if (!size->gothic->font->get_glyph(size->gothic, glyph,
sjis_code_to_unicode(code), weight))
return false;
// Align the gothic glyph toward the bottom of the FNL line.
glyph->rect.y += size->fnl->y_offset - size->gothic->y_offset;
glyph->rect.y -= lroundf((size->fnl->size - size->gothic->size)
* SR_FALLBACK_VERTICAL_SHIFT_RATIO);
return true;
}
static float sr_fallback_size_char(struct font_size *_size, uint32_t code)
{
struct sr_fallback_font_size *size = (struct sr_fallback_font_size*)_size;
float width = size->fnl->font->size_char(size->fnl, code);
if (width != 0.0f)
return width;
return size->gothic->font->size_char(size->gothic,
sjis_code_to_unicode(code));
}
static float sr_fallback_size_char_kerning(struct font_size *size, uint32_t code,
uint32_t code_next)
{
struct sr_fallback_font_size *fallback = (struct sr_fallback_font_size*)size;
float width = fallback->fnl->font->size_char_kerning(fallback->fnl,
code, code_next);
if (width != 0.0f)
return width;
return fallback->gothic->font->size_char_kerning(fallback->gothic,
sjis_code_to_unicode(code), sjis_code_to_unicode(code_next));
}
static struct font sr_fallback_font = {
.charmap = CHARMAP_SJIS,
.get_glyph = sr_fallback_get_glyph,
.size_char = sr_fallback_size_char,
.size_char_kerning = sr_fallback_size_char_kerning,
};
static struct font_size *sr_get_fallback_size(struct font_size *fnl)
{
// One wrapper per FNL size.
for (struct sr_fallback_font_size *size = sr_fallback_sizes; size;
size = size->next) {
if (size->fnl == fnl)
return &size->super;
}
struct sr_fallback_font_size *size = xcalloc(1, sizeof(*size));
size->super.size = fnl->size;
size->super.y_offset = fnl->y_offset;
size->super.font = &sr_fallback_font;
size->fnl = fnl;
size->gothic = gfx_font_get_size(FONT_GOTHIC,
fnl->size * SR_FALLBACK_FONT_SCALE);
size->next = sr_fallback_sizes;
sr_fallback_sizes = size;
return &size->super;
}
static void sr_prepare_text_style(struct text_style *ts)
{
if (ts->font_size)
return;
struct font_size *size = gfx_font_get_size(ts->face, ts->size);
// Install the wrapper only on FNL text styles.
ts->font_size = size->font->charmap == CHARMAP_SJIS
? sr_get_fallback_size(size) : size;
}
static struct sr_text_properties default_text_properties = {
.x = 0.0,
.y = 0,
@@ -134,6 +233,7 @@ static void SengokuRanceFont_SetEdgeWidth(float w)
static float SengokuRanceFont_GetTextWidth(struct string *str)
{
sr_prepare_text_style(&current_text_properties.ts);
return gfx_size_text(&current_text_properties.ts, str->text);
}
@@ -326,6 +426,7 @@ static float sp_text_draw(struct sact_sprite *sp, struct sr_text_properties *tp,
if (tp->ts.size < 8) {
tp->ts.size = 8;
}
sr_prepare_text_style(&tp->ts);
if (!sp->text.texture.handle) {
gfx_init_texture_rgba(&sp->text.texture, sp->rect.w, sp->rect.h, COLOR(0, 0, 0, 0));
+8 -6
View File
@@ -45,13 +45,14 @@ HLL_WARN_UNIMPLEMENTED( , void, StoatSpriteEngine, SetVolumeMixerMasterGroupNum,
HLL_WARN_UNIMPLEMENTED( , void, StoatSpriteEngine, SetVolumeMixerSEGroupNum, int n);
HLL_WARN_UNIMPLEMENTED( , void, StoatSpriteEngine, SetVolumeMixerBGMGroupNum, int n);
static int StoatSpriteEngine_SP_SetDrawMethod(int sp_no, int method)
int StoatSpriteEngine_SP_SetDrawMethod(int sp_no, int method)
{
struct sact_sprite *sp = sact_get_sprite(sp_no);
if (!sp) return 0;
switch (method) {
case 0: return sprite_set_draw_method(sp, DRAW_METHOD_NORMAL);
case 1: return sprite_set_draw_method(sp, DRAW_METHOD_ADDITIVE);
case 2: return sprite_set_draw_method(sp, DRAW_METHOD_MULTIPLY);
case 3: return sprite_set_draw_method(sp, DRAW_METHOD_SCREEN);
default:
WARNING("unknown draw method: %d", method);
@@ -59,7 +60,7 @@ static int StoatSpriteEngine_SP_SetDrawMethod(int sp_no, int method)
}
}
static int StoatSpriteEngine_SP_GetDrawMethod(int sp_no)
int StoatSpriteEngine_SP_GetDrawMethod(int sp_no)
{
struct sact_sprite *sp = sact_try_get_sprite(sp_no);
if (!sp) return DRAW_METHOD_NORMAL;
@@ -67,6 +68,7 @@ static int StoatSpriteEngine_SP_GetDrawMethod(int sp_no)
switch (method) {
case DRAW_METHOD_NORMAL: return 0;
case DRAW_METHOD_ADDITIVE: return 1;
case DRAW_METHOD_MULTIPLY: return 2;
case DRAW_METHOD_SCREEN: return 3;
default:
WARNING("unknown draw method: %d", method);
@@ -111,7 +113,7 @@ bool StoatSpriteEngine_SP_SetTextSprite(int sp_no, struct string *text)
char s[3];
extract_sjis_char(text->text, s);
int w = ceilf(text_style_width(&text_sprite_ts, s));
int w = ceilf(gfx_size_char(&text_sprite_ts, s));
int h = text_sprite_ts.size;
// XXX: System40.exe lies about the height of half-width characters.
// E.g. a size 64 letter "H" is reported as 32 pixels tall.
@@ -144,7 +146,7 @@ void StoatSpriteEngine_SP_SetTextSpriteColor(int r, int g, int b)
void StoatSpriteEngine_SP_SetTextSpriteBoldWeight(float weight)
{
//NOTICE("StoatSpriteEngine.SP_SetTextSpriteBoldWeight(%f)", weight);
text_sprite_ts.bold_width = weight;
}
void StoatSpriteEngine_SP_SetTextSpriteEdgeWeight(float weight)
@@ -186,8 +188,8 @@ float StoatSpriteEngine_FPS_Get(void)
HLL_WARN_UNIMPLEMENTED( , void, StoatSpriteEngine, VIEW_SetOffsetPos, int x, int y);
static bool keep_previous_view = true;
HLL_WARN_UNIMPLEMENTED((keep_previous_view = on, true), bool, StoatSpriteEngine, KeepPreviousView_SetMode, bool on);
HLL_WARN_UNIMPLEMENTED(keep_previous_view, bool, StoatSpriteEngine, KeepPreviousView_GetMode);
HLL_QUIET_UNIMPLEMENTED((keep_previous_view = on, true), bool, StoatSpriteEngine, KeepPreviousView_SetMode, bool on);
HLL_QUIET_UNIMPLEMENTED(keep_previous_view, bool, StoatSpriteEngine, KeepPreviousView_GetMode);
/*
* NOTE: The multisprite "type" alters the behavior of the multisprite functions.
+45 -7
View File
@@ -69,11 +69,19 @@ HLL_WARN_UNIMPLEMENTED(false, bool, SystemService, AddURLMenu, struct string *ti
static bool SystemService_IsFullScreen(void)
{
return false;
return gfx_is_fullscreen();
}
static bool SystemService_ChangeNormalScreen(void)
{
return gfx_set_fullscreen(false);
}
static bool SystemService_ChangeFullScreen(void)
{
return gfx_set_fullscreen(true);
}
//bool SystemService_ChangeNormalScreen(void);
HLL_WARN_UNIMPLEMENTED(false, bool, SystemService, ChangeFullScreen);
HLL_WARN_UNIMPLEMENTED(false, bool, SystemService, InitMainWindowPosAndSize);
//static bool SystemService_UpdateView(void);
@@ -103,6 +111,8 @@ static bool SystemService_SetHideMouseCursorByGame(bool hide)
//bool SystemService_GetHideMouseCursorByGame(void);
HLL_WARN_UNIMPLEMENTED(false, bool, SystemService, SetUsePower2Texture, bool use);
//bool SystemService_GetUsePower2Texture(void);
HLL_WARN_UNIMPLEMENTED(true, bool, SystemService, SetAntiAliasingMode, int mode);
//int SystemService_GetAntiAliasingMode(void);
enum window_settings_asect_ratio {
ASPECT_RATIO_NORMAL,
@@ -325,7 +335,10 @@ static void SystemService_OpenPlayingManual(void) {
if (!SystemService_IsExistPlayingManual()) {
return;
}
#ifdef __ANDROID__
const int COMMAND_OPEN_PLAYING_MANUAL = 0x8000;
SDL_AndroidSendMessage(COMMAND_OPEN_PLAYING_MANUAL, 0);
#else
char *filename = get_manual_filename();
char *real_path = realpath_utf8(filename);
@@ -352,6 +365,7 @@ static void SystemService_OpenPlayingManual(void) {
free(url);
free(path_component);
#endif
}
//static bool SystemService_IsExistSystemMessage(void);
@@ -362,26 +376,45 @@ static void SystemService_RestrainScreensaver(void) { }
//static int SystemService_Debug_GetUseVideoMemorySize(void);
// Rance 01
static void SystemService_Rance0123456789(struct string **text)
{
*text = cstr_to_string("-RANCE010ECNAR-"); // ???
*text = cstr_to_string("-RANCE010ECNAR-");
}
// Rance 01 trial edition
static void SystemService_XXXXX01XXXXXXXX(struct string **text)
{
*text = cstr_to_string("RANCE01RANCEKAKKOII");
}
// Drapeko
static void SystemService_Test(struct string **text)
{
*text = cstr_to_string("DELETE ALL 758490275489207548093");
}
// Drapeko trial edition
static void SystemService_DRPKT(struct string **text)
{
*text = cstr_to_string("DRPKT QWERTY NUFUAUEO 75849027582754829");
}
// Rance 9
static void SystemService_Rance96161988(struct string **text) {
*text = cstr_to_string("=Rance99/RANCE99=");
}
// Pascha3 Plus Contents
static void SystemService_XXX(struct string **text) {
*text = cstr_to_string("FORMAT HDD ERASE 578205024758284076520478254092784789752384758204687293");
}
// Busou Shoujotai Blade Briders
static void SystemService_GetSystemBufferName(struct string **text) {
*text = cstr_to_string("xxx4wxx:xxxx-xxxxxfx5x3");
}
static void SystemService_PreLink(void);
static void SystemService_ModuleInit(void)
@@ -404,7 +437,7 @@ HLL_LIBRARY(SystemService,
HLL_EXPORT(GetGameName, SystemService_GetGameName),
HLL_EXPORT(AddURLMenu, SystemService_AddURLMenu),
HLL_EXPORT(IsFullScreen, SystemService_IsFullScreen),
HLL_TODO_EXPORT(ChangeNormalScreen, SystemService_ChangeNormalScreen),
HLL_EXPORT(ChangeNormalScreen, SystemService_ChangeNormalScreen),
HLL_EXPORT(ChangeFullScreen, SystemService_ChangeFullScreen),
HLL_EXPORT(InitMainWindowPosAndSize, SystemService_InitMainWindowPosAndSize),
HLL_EXPORT(UpdateView, SystemService_UpdateView),
@@ -415,6 +448,8 @@ HLL_LIBRARY(SystemService,
HLL_TODO_EXPORT(GetHideMouseCursorByGame, SystemService_GetHideMouseCursorByGame),
HLL_EXPORT(SetUsePower2Texture, SystemService_SetUsePower2Texture),
HLL_TODO_EXPORT(GetUsePower2Texture, SystemService_GetUsePower2Texture),
HLL_EXPORT(SetAntiAliasingMode, SystemService_SetAntiAliasingMode),
HLL_TODO_EXPORT(GetAntiAliasingMode, SystemService_GetAntiAliasingMode),
HLL_EXPORT(SetWindowSetting, SystemService_SetWindowSetting),
HLL_EXPORT(GetWindowSetting, SystemService_GetWindowSetting),
HLL_EXPORT(SetMouseCursorConfig, SystemService_SetMouseCursorConfig),
@@ -433,8 +468,11 @@ HLL_LIBRARY(SystemService,
HLL_TODO_EXPORT(Debug_GetUseVideoMemorySize, SystemService_Debug_GetUseVideoMemorySize),
HLL_EXPORT(Rance0123456789, SystemService_Rance0123456789),
HLL_EXPORT(XXXXX01XXXXXXXX, SystemService_XXXXX01XXXXXXXX),
HLL_EXPORT(GetSystemBufferName, SystemService_GetSystemBufferName),
HLL_EXPORT(XXX, SystemService_XXX),
HLL_EXPORT(Test, SystemService_Test),
HLL_EXPORT(DRPKT, SystemService_DRPKT)
HLL_EXPORT(DRPKT, SystemService_DRPKT),
HLL_EXPORT(Rance96161988, SystemService_Rance96161988)
);
static struct ain_hll_function *get_fun(int libno, const char *name)
+3 -4
View File
@@ -17,13 +17,12 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <zlib.h>
#include "system4/little_endian.h"
#include "system4/buffer.h"
#include "system4/file.h"
#include "system4/string.h"
#include "system4/utfsjis.h"
#include "system4/zlib.h"
#include "hll.h"
#include "vm/heap.h"
@@ -83,9 +82,9 @@ static uint8_t *read_compressed_data(FILE *fp, uint32_t *size)
free(buf);
return NULL;
}
unsigned long raw_size = LittleEndian_getDW(buf, 0);
size_t raw_size = LittleEndian_getDW(buf, 0);
void *raw = malloc(raw_size);
if (uncompress(raw, &raw_size, buf + 4, *size - 4) != Z_OK) {
if (!zlib_decompress_exact(raw, raw_size, buf + 4, *size - 4)) {
WARNING("uncompress failed");
free(raw);
free(buf);
+1 -11
View File
@@ -80,19 +80,9 @@ static bool vsf_write_type(enum vsf_valuetype type)
return vsf_write(&b, 1);
}
static char *process_filename(struct string *filename)
{
char *u = sjis2utf(filename->text, filename->size);
for (int i = 0; u[i]; i++) {
if (u[i] == '\\')
u[i] = '/';
}
return u;
}
static bool vsfile_open(struct string *filename, bool read)
{
char *u = process_filename(filename);
char *u = gamedir_path(filename->text);
if (vs_file) {
WARNING("VSFile is already opened");
fclose(vs_file);
+32
View File
@@ -0,0 +1,32 @@
/* Copyright (C) 2026 kichikuou <KichikuouChrome@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://gnu.org/licenses/>.
*/
#include "hll.h"
static int ValueEncryption_Get(int code, int backup_code, int xor_key)
{
return code;
}
static void ValueEncryption_Set(int value, int *code, int *backup_code, int xor_key)
{
*code = *backup_code = value;
}
HLL_LIBRARY(ValueEncryption,
HLL_EXPORT(Get, ValueEncryption_Get),
HLL_EXPORT(Set, ValueEncryption_Set)
);
+1 -1
View File
@@ -39,7 +39,7 @@ static int vmSound_LoadMemory(int channel, int memory)
struct archive_data *dfile = chr_loader_get_blob_as_archive_data(memory);
if (!dfile)
return 0;
return wav_prepare_from_archive_data(channel, dfile);
return wav_prepare_from_archive_data(channel, AUDIO_NO_METADATA, dfile);
}
static void vmSound_Unload(int channel, int length)
+48 -11
View File
@@ -24,6 +24,7 @@
#include "input.h"
#include "scene.h"
#include "vm.h"
#include "xsystem4.h"
#include "debugger.h"
#ifndef M_PI
@@ -112,6 +113,7 @@ static enum sact_keycode sdl_keytable[] = {
[SDL_SCANCODE_KP_MINUS] = VK_SUBTRACT,
[SDL_SCANCODE_KP_PERIOD] = VK_DECIMAL,
[SDL_SCANCODE_KP_DIVIDE] = VK_DIVIDE,
[SDL_SCANCODE_KP_ENTER] = VK_RETURN,
[SDL_SCANCODE_F1] = VK_F1,
[SDL_SCANCODE_F2] = VK_F2,
[SDL_SCANCODE_F3] = VK_F3,
@@ -140,6 +142,29 @@ static enum sact_keycode sdl_keytable[] = {
[SDL_SCANCODE_SCROLLLOCK] = VK_SCROLL,
};
static enum sact_keycode sdl_to_sact_key(SDL_Scancode scancode, SDL_Keymod modifiers)
{
if (!(modifiers & KMOD_NUM)) {
switch (scancode) {
case SDL_SCANCODE_KP_0: return VK_INSERT;
case SDL_SCANCODE_KP_1: return VK_END;
case SDL_SCANCODE_KP_2: return VK_DOWN;
case SDL_SCANCODE_KP_3: return VK_NEXT;
case SDL_SCANCODE_KP_4: return VK_LEFT;
case SDL_SCANCODE_KP_5: return VK_CLEAR;
case SDL_SCANCODE_KP_6: return VK_RIGHT;
case SDL_SCANCODE_KP_7: return VK_HOME;
case SDL_SCANCODE_KP_8: return VK_UP;
case SDL_SCANCODE_KP_9: return VK_PRIOR;
case SDL_SCANCODE_KP_PERIOD: return VK_DELETE;
default: break;
}
}
if (scancode >= sizeof(sdl_keytable) / sizeof(*sdl_keytable))
return 0;
return sdl_keytable[scancode];
}
bool mouse_focus = true;
bool keyboard_focus = true;
@@ -231,13 +256,18 @@ bool mouse_show_cursor(bool show)
return SDL_ShowCursor(show ? SDL_ENABLE : SDL_DISABLE) >= 0;
}
static void(*input_handler)(const char*);
static void(*editing_handler)(const char*, int, int);
static void(*key_handler)(int);
static void key_event(SDL_KeyboardEvent *e, bool pressed)
{
if (e->keysym.scancode >= (sizeof(sdl_keytable)/sizeof(*sdl_keytable)))
return;
enum sact_keycode code = sdl_keytable[e->keysym.scancode];
if (code)
enum sact_keycode code = sdl_to_sact_key(e->keysym.scancode, e->keysym.mod);
if (code) {
key_state[code] = pressed;
if (pressed && key_handler)
key_handler(code);
}
}
static void mouse_event(SDL_MouseButtonEvent *e)
@@ -495,9 +525,6 @@ static void controller_button_event(SDL_ControllerButtonEvent *e)
joybutton_state[e->button] = e->state == SDL_PRESSED;
}
static void(*input_handler)(const char*);
static void(*editing_handler)(const char*, int, int);
void register_input_handler(void(*handler)(const char*))
{
input_handler = handler;
@@ -518,6 +545,16 @@ void clear_editing_handler(void)
editing_handler = NULL;
}
void register_key_handler(void(*handler)(int))
{
key_handler = handler;
}
void clear_key_handler(void)
{
key_handler = NULL;
}
static void fire_deferred_events(void)
{
uint32_t now = SDL_GetTicks();
@@ -608,13 +645,14 @@ void handle_events(void)
case SDL_WINDOWEVENT:
handle_window_event(&e);
break;
case SDL_APP_DIDENTERFOREGROUND:
gfx_swap();
break;
case SDL_KEYDOWN:
if (e.key.keysym.scancode == SDL_SCANCODE_F9) {
vm_stack_trace();
} else if (e.key.keysym.scancode == SDL_SCANCODE_F11) {
uint32_t flag = SDL_WINDOW_FULLSCREEN_DESKTOP;
bool fs = SDL_GetWindowFlags(sdl.window) & flag;
SDL_SetWindowFullscreen(sdl.window, fs ? 0 : flag);
gfx_toggle_fullscreen();
} else if (e.key.keysym.scancode == SDL_SCANCODE_S) {
if (e.key.keysym.mod & (KMOD_LALT | KMOD_RALT)) {
screenshot_save();
@@ -728,4 +766,3 @@ void handle_events(void)
if (dbg_dap)
dbg_dap_handle_messages();
}
+12
View File
@@ -40,10 +40,13 @@ xsystem4 = [version_h,
'3d/collision.c',
'3d/debug.c',
'3d/model.c',
'3d/mpr.c',
'3d/parser.c',
'3d/particle.c',
'3d/reign.c',
'3d/renderer.c',
'3d/s3de.c',
'3d/save.c',
'dungeon/dgn.c',
'dungeon/dtx.c',
@@ -59,7 +62,10 @@ xsystem4 = [version_h,
'parts/construction.c',
'parts/debug.c',
'parts/flash.c',
'parts/flat.c',
'parts/input.c',
'parts/layoutbox.c',
'parts/message.c',
'parts/motion.c',
'parts/parts.c',
'parts/render.c',
@@ -68,6 +74,7 @@ xsystem4 = [version_h,
'hll/ACXLoader.c',
'hll/ADVSYS.c',
'hll/AFAFactory.c',
'hll/AliceLogo.c',
'hll/AliceLogo2.c',
'hll/AliceLogo3.c',
@@ -92,6 +99,7 @@ xsystem4 = [version_h,
'hll/DALKEDemo.c',
'hll/Data.c',
'hll/DataFile.c',
'hll/Discord.c',
'hll/DrawDungeon.c',
'hll/DrawEffect.c',
'hll/DrawGraph.c',
@@ -116,6 +124,7 @@ xsystem4 = [version_h,
'hll/InputString.c',
'hll/KiwiSoundEngine.c',
'hll/LoadCG.c',
'hll/MADLoader.c',
'hll/MainEXFile.c',
'hll/MainSurface.c',
'hll/MamanyoDemo.c',
@@ -131,6 +140,8 @@ xsystem4 = [version_h,
'hll/MusicSystem.c',
'hll/NewFont.c',
'hll/OutputLog.c',
'hll/P3MapSprite.c',
'hll/P3SquareSprite.c',
'hll/PassRegister.c',
'hll/PastelChime2.c',
'hll/PartsEngine.c',
@@ -172,6 +183,7 @@ xsystem4 = [version_h,
'hll/vmSurface.c',
'hll/vmSystem.c',
'hll/vmTimer.c',
'hll/ValueEncryption.c',
'hll/VSFile.c',
]
+1 -1
View File
@@ -175,7 +175,7 @@ static int audio_callback(sts_mixer_sample_t *sample, void *data)
return STS_STREAM_COMPLETE;
}
const int nr_channels = 2;
unsigned samples = mc->audio.frame->linesize[0] / mc->bytes_per_sample;
unsigned samples = mc->audio.frame->nb_samples;
if (samples * nr_channels != sample->length) {
free(sample->data);
sample->length = samples * nr_channels;
+19 -3
View File
@@ -194,7 +194,7 @@ void variable_set(struct page *page, int varno, enum ain_data_type type, union v
void delete_page_vars(struct page *page)
{
for (int i = 0; i < page->nr_vars; i++) {
for (int i = page->nr_vars - 1; i >= 0; i--) {
variable_fini(page->values[i], variable_type(page, i, NULL, NULL), true);
}
}
@@ -292,7 +292,7 @@ struct page *alloc_array(int rank, union vm_value *dimensions, enum ain_data_typ
data_type = unref_array_type(data_type);
enum ain_data_type type = array_type(data_type);
struct page *page = alloc_page(ARRAY_PAGE, data_type, dimensions->i);
struct page *page = alloc_page(ARRAY_PAGE, data_type, max(0, dimensions->i));
page->array.struct_type = struct_type;
page->array.rank = rank;
@@ -557,6 +557,17 @@ static int array_compare_custom(const void *_a, const void *_b)
return d ? d : a->index - b->index;
}
static int array_compare_custom_string(const void *_a, const void *_b)
{
const struct sortable *a = _a;
const struct sortable *b = _b;
stack_push(vm_string_ref(heap_get_string(a->v.i)));
stack_push(vm_string_ref(heap_get_string(b->v.i)));
vm_call(current_sort_function, -1);
int d = stack_pop().i;
return d ? d : a->index - b->index;
}
void array_sort(struct page *page, int compare_fno)
{
if (!page)
@@ -569,7 +580,8 @@ void array_sort(struct page *page, int compare_fno)
values[i].index = i;
}
current_sort_function = compare_fno;
qsort(values, page->nr_vars, sizeof(struct sortable), array_compare_custom);
qsort(values, page->nr_vars, sizeof(struct sortable),
page->a_type == AIN_ARRAY_STRING ? array_compare_custom_string : array_compare_custom);
for (int i = 0; i < page->nr_vars; i++) {
page->values[i] = values[i].v;
}
@@ -691,6 +703,8 @@ void array_reverse(struct page *page)
struct page *delegate_new_from_method(int obj, int fun)
{
if (fun < 1)
return alloc_page(DELEGATE_PAGE, 0, 0);
struct page *page = alloc_page(DELEGATE_PAGE, 0, 3);
page->values[0].i = obj;
page->values[1].i = fun;
@@ -713,6 +727,8 @@ bool delegate_contains(struct page *dst, int obj, int fun)
struct page *delegate_append(struct page *dst, int obj, int fun)
{
if (fun < 1)
return dst;
if (!dst)
return delegate_new_from_method(obj, fun);
if (dst->type != DELEGATE_PAGE)
+149 -38
View File
@@ -34,13 +34,19 @@ void parts_cp_op_free(struct parts_cp_op *op)
switch (op->type) {
case PARTS_CP_CREATE:
case PARTS_CP_CREATE_PIXEL_ONLY:
case PARTS_CP_CG:
case PARTS_CP_FILL:
case PARTS_CP_FILL_ALPHA_COLOR:
case PARTS_CP_FILL_AMAP:
case PARTS_CP_FILL_WITH_ALPHA:
case PARTS_CP_DRAW_RECT:
case PARTS_CP_GRAY_FILTER:
break;
case PARTS_CP_CG:
free_string(op->cg.name);
break;
case PARTS_CP_DRAW_CUT_CG:
case PARTS_CP_COPY_CUT_CG:
free_string(op->cut_cg.cg_name);
break;
case PARTS_CP_DRAW_TEXT:
case PARTS_CP_COPY_TEXT:
@@ -90,8 +96,7 @@ bool PE_AddCreateCGToProcess(int parts_no, struct string *cg_name, int state)
if (!parts_state_valid(--state))
return false;
int no;
if (!asset_exists_by_name(ASSET_CG, cg_name->text, &no)) {
if (!asset_exists_by_name(ASSET_CG, cg_name->text, NULL)) {
WARNING("Invalid CG name: %s", display_sjis0(cg_name->text));
return false;
}
@@ -99,7 +104,7 @@ bool PE_AddCreateCGToProcess(int parts_no, struct string *cg_name, int state)
struct parts_construction_process *cproc = get_cproc(parts_no, state);
struct parts_cp_op *op = xcalloc(1, sizeof(struct parts_cp_op));
op->type = PARTS_CP_CG;
op->cg.no = no;
op->cg.name = string_ref(cg_name);
parts_add_cp_op(cproc, op);
return true;
}
@@ -156,7 +161,23 @@ bool PE_AddFillAMapToPartsConstructionProcess(int parts_no, int x, int y, int w,
}
bool PE_AddFillWithAlphaToPartsConstructionProcess(int parts_no, int x, int y, int w, int h,
int r, int g, int b, int a, int state);
int r, int g, int b, int a, int state)
{
if (!parts_state_valid(--state))
return false;
struct parts_construction_process *cproc = get_cproc(parts_no, state);
struct parts_cp_op *op = xcalloc(1, sizeof(struct parts_cp_op));
op->type = PARTS_CP_FILL_WITH_ALPHA;
op->fill = (struct parts_cp_fill) {
.x = x, .y = y, .w = w, .h = h,
.r = r, .g = g, .b = b, .a = a
};
parts_add_cp_op(cproc, op);
return true;
}
bool PE_AddFillGradationHorizonToPartsConstructionProcess(int parts_no, int x, int y, int w, int h,
int top_r, int top_g, int top_b, int bot_r, int bot_g, int bot_b, int state);
@@ -184,8 +205,7 @@ bool PE_AddDrawCutCGToPartsConstructionProcess(int parts_no, struct string *cg_n
if (!parts_state_valid(--state))
return false;
int cg_no;
if (!asset_exists_by_name(ASSET_CG, cg_name->text, &cg_no)) {
if (!asset_exists_by_name(ASSET_CG, cg_name->text, NULL)) {
WARNING("Invalid CG name: %s", display_sjis0(cg_name->text));
return false;
}
@@ -194,7 +214,7 @@ bool PE_AddDrawCutCGToPartsConstructionProcess(int parts_no, struct string *cg_n
struct parts_cp_op *op = xcalloc(1, sizeof(struct parts_cp_op));
op->type = PARTS_CP_DRAW_CUT_CG;
op->cut_cg = (struct parts_cp_cut_cg) {
.cg_no = cg_no,
.cg_name = string_ref(cg_name),
.dx = dx, .dy = dy, .dw = dw, .dh = dh,
.sx = sx, .sy = sy, .sw = sw, .sh = sh,
.interp_type = interp_type
@@ -210,8 +230,7 @@ bool PE_AddCopyCutCGToPartsConstructionProcess(int parts_no, struct string *cg_n
if (!parts_state_valid(--state))
return false;
int cg_no;
if (!asset_exists_by_name(ASSET_CG, cg_name->text, &cg_no)) {
if (!asset_exists_by_name(ASSET_CG, cg_name->text, NULL)) {
WARNING("Invalid CG name: %s", display_sjis0(cg_name->text));
return false;
}
@@ -220,7 +239,7 @@ bool PE_AddCopyCutCGToPartsConstructionProcess(int parts_no, struct string *cg_n
struct parts_cp_op *op = xcalloc(1, sizeof(struct parts_cp_op));
op->type = PARTS_CP_COPY_CUT_CG;
op->cut_cg = (struct parts_cp_cut_cg) {
.cg_no = cg_no,
.cg_name = string_ref(cg_name),
.dx = dx, .dy = dy, .dw = dw, .dh = dh,
.sx = sx, .sy = sy, .sw = sw, .sh = sh,
.interp_type = interp_type
@@ -231,7 +250,23 @@ bool PE_AddCopyCutCGToPartsConstructionProcess(int parts_no, struct string *cg_n
}
bool PE_AddGrayFilterToPartsConstructionProcess(int parts_no, int x, int y, int w, int h,
bool full_size, int state);
bool full_size, int state)
{
if (!parts_state_valid(--state))
return false;
struct parts_construction_process *cproc = get_cproc(parts_no, state);
struct parts_cp_op *op = xcalloc(1, sizeof(struct parts_cp_op));
op->type = PARTS_CP_GRAY_FILTER;
op->filter = (struct parts_cp_filter) {
.x = x, .y = y, .w = w, .h = h,
.full_size = full_size
};
parts_add_cp_op(cproc, op);
return true;
}
bool PE_AddAddFilterToPartsConstructionProcess(int parts_no, int x, int y, int w, int h,
int r, int g, int b, bool full_size, int state);
bool PE_AddMulFilterToPartsConstructionProcess(int parts_no, int x, int y, int w, int h,
@@ -297,60 +332,91 @@ bool PE_AddCopyTextToPartsConstructionProcess(int parts_no, int x, int y, struct
PARTS_CP_COPY_TEXT);
}
static void build_create(struct parts *parts, struct parts_construction_process *cproc,
static bool build_create(struct parts *parts, struct parts_construction_process *cproc,
struct parts_cp_create *op)
{
gfx_delete_texture(&cproc->common.texture);
gfx_init_texture_rgba(&cproc->common.texture, op->w, op->h, (SDL_Color){0,0,0,255});
if (!cproc->common.texture.handle)
return false;
parts_set_dims(parts, &cproc->common, op->w, op->h);
return true;
}
static void build_create_pixel_only(struct parts *parts, struct parts_construction_process *cproc,
static bool build_create_pixel_only(struct parts *parts, struct parts_construction_process *cproc,
struct parts_cp_create *op)
{
gfx_delete_texture(&cproc->common.texture);
gfx_init_texture_rgb(&cproc->common.texture, op->w, op->h, (SDL_Color){0,0,0,255});
if (!cproc->common.texture.handle)
return false;
parts_set_dims(parts, &cproc->common, op->w, op->h);
return true;
}
static void build_cg(struct parts *parts, struct parts_construction_process *cproc, struct parts_cp_cg *op)
static bool build_cg(struct parts *parts, struct parts_construction_process *cproc, struct parts_cp_cg *op)
{
struct cg *cg = asset_cg_load(op->no);
assert(cg);
struct cg *cg = asset_cg_load_by_name(op->name->text, NULL);
if (!cg)
return false;
gfx_delete_texture(&cproc->common.texture);
gfx_init_texture_with_cg(&cproc->common.texture, cg);
parts_set_dims(parts, &cproc->common, cg->metrics.w, cg->metrics.h);
cg_free(cg);
return true;
}
static void build_fill(struct parts_construction_process *cproc, struct parts_cp_fill *op)
static bool build_fill(struct parts_construction_process *cproc, struct parts_cp_fill *op)
{
if (!cproc->common.texture.handle)
return false;
gfx_fill(&cproc->common.texture, op->x, op->y, op->w, op->h, op->r, op->g, op->b);
return true;
}
static void build_fill_alpha_color(struct parts_construction_process *cproc, struct parts_cp_fill *op)
static bool build_fill_alpha_color(struct parts_construction_process *cproc, struct parts_cp_fill *op)
{
if (!cproc->common.texture.handle)
return false;
gfx_fill_alpha_color(&cproc->common.texture, op->x, op->y, op->w, op->h, op->r, op->g, op->b, op->a);
return true;
}
static void build_fill_amap(struct parts_construction_process *cproc, struct parts_cp_fill *op)
static bool build_fill_amap(struct parts_construction_process *cproc, struct parts_cp_fill *op)
{
if (!cproc->common.texture.handle)
return false;
gfx_fill_amap(&cproc->common.texture, op->x, op->y, op->w, op->h, op->a);
return true;
}
static void build_draw_rect(struct parts_construction_process *cproc, struct parts_cp_fill *op)
static bool build_fill_with_alpha(struct parts_construction_process *cproc, struct parts_cp_fill *op)
{
if (!cproc->common.texture.handle)
return false;
gfx_fill_with_alpha(&cproc->common.texture, op->x, op->y, op->w, op->h, op->r, op->g, op->b, op->a);
return true;
}
static bool build_draw_rect(struct parts_construction_process *cproc, struct parts_cp_fill *op)
{
if (!cproc->common.texture.handle)
return false;
int x2 = op->x + op->w - 1;
int y2 = op->y + op->h - 1;
gfx_draw_line(&cproc->common.texture, op->x, op->y, x2, op->y, op->r, op->g, op->b);
gfx_draw_line(&cproc->common.texture, op->x, op->y, op->x, y2, op->r, op->g, op->b);
gfx_draw_line(&cproc->common.texture, x2, op->y, x2, y2, op->r, op->g, op->b);
gfx_draw_line(&cproc->common.texture, op->x, y2, x2, y2, op->r, op->g, op->b);
return true;
}
static void build_draw_cut_cg(struct parts_construction_process *cproc, struct parts_cp_cut_cg *op)
static bool build_draw_cut_cg(struct parts_construction_process *cproc, struct parts_cp_cut_cg *op)
{
struct cg *cg = asset_cg_load(op->cg_no);
if (!cproc->common.texture.handle)
return false;
struct cg *cg = asset_cg_load_by_name(op->cg_name->text, NULL);
assert(cg);
Texture src;
@@ -360,11 +426,15 @@ static void build_draw_cut_cg(struct parts_construction_process *cproc, struct p
gfx_copy_stretch_blend_amap(&cproc->common.texture, op->dx, op->dy, op->dw, op->dh,
&src, op->sx, op->sy, op->sw, op->sh);
gfx_delete_texture(&src);
return true;
}
static void build_copy_cut_cg(struct parts_construction_process *cproc, struct parts_cp_cut_cg *op)
static bool build_copy_cut_cg(struct parts_construction_process *cproc, struct parts_cp_cut_cg *op)
{
struct cg *cg = asset_cg_load(op->cg_no);
if (!cproc->common.texture.handle)
return false;
struct cg *cg = asset_cg_load_by_name(op->cg_name->text, NULL);
assert(cg);
Texture src;
@@ -374,20 +444,42 @@ static void build_copy_cut_cg(struct parts_construction_process *cproc, struct p
gfx_copy_stretch_with_alpha_map(&cproc->common.texture, op->dx, op->dy, op->dw, op->dh,
&src, op->sx, op->sy, op->sw, op->sh);
gfx_delete_texture(&src);
return true;
}
static void build_draw_text(struct parts_construction_process *cproc, struct parts_cp_text *op)
static bool build_draw_text(struct parts_construction_process *cproc, struct parts_cp_text *op)
{
if (!cproc->common.texture.handle)
return false;
gfx_render_text(&cproc->common.texture, op->x, op->y, op->text->text, &op->style, true);
return true;
}
static void build_copy_text(struct parts_construction_process *cproc, struct parts_cp_text *op)
static bool build_copy_text(struct parts_construction_process *cproc, struct parts_cp_text *op)
{
if (!cproc->common.texture.handle)
return false;
int w = ceilf(gfx_size_text (&op->style, op->text->text));
int h = ceilf(op->style.size + op->style.edge_up + op->style.edge_down);
gfx_fill_with_alpha(&cproc->common.texture, op->x, op->y, w, h,
op->style.edge_color.r, op->style.edge_color.g, op->style.edge_color.b, 0);
gfx_render_text(&cproc->common.texture, op->x, op->y, op->text->text, &op->style, false);
return true;
}
static bool build_gray_filter(struct parts_construction_process *cproc, struct parts_cp_filter *op)
{
if (!cproc->common.texture.handle)
return false;
int x = op->x, y = op->y, w = op->w, h = op->h;
if (op->full_size) {
x = 0; y = 0;
w = cproc->common.texture.w;
h = cproc->common.texture.h;
}
gfx_copy_grayscale(&cproc->common.texture, x, y, &cproc->common.texture, x, y, w, h);
return true;
}
bool parts_build_construction_process(struct parts *parts,
@@ -397,37 +489,56 @@ bool parts_build_construction_process(struct parts *parts,
TAILQ_FOREACH(op, &cproc->ops, entry) {
switch (op->type) {
case PARTS_CP_CREATE:
build_create(parts, cproc, &op->create);
if (!build_create(parts, cproc, &op->create))
return false;
break;
case PARTS_CP_CREATE_PIXEL_ONLY:
build_create_pixel_only(parts, cproc, &op->create);
if (!build_create_pixel_only(parts, cproc, &op->create))
return false;
break;
case PARTS_CP_CG:
build_cg(parts, cproc, &op->cg);
if (!build_cg(parts, cproc, &op->cg))
return false;
break;
case PARTS_CP_FILL:
build_fill(cproc, &op->fill);
if (!build_fill(cproc, &op->fill))
return false;
break;
case PARTS_CP_FILL_ALPHA_COLOR:
build_fill_alpha_color(cproc, &op->fill);
if (!build_fill_alpha_color(cproc, &op->fill))
return false;
break;
case PARTS_CP_FILL_AMAP:
build_fill_amap(cproc, &op->fill);
if (!build_fill_amap(cproc, &op->fill))
return false;
break;
case PARTS_CP_FILL_WITH_ALPHA:
if (!build_fill_with_alpha(cproc, &op->fill))
return false;
break;
case PARTS_CP_DRAW_RECT:
build_draw_rect(cproc, &op->fill);
if (!build_draw_rect(cproc, &op->fill))
return false;
break;
case PARTS_CP_DRAW_CUT_CG:
build_draw_cut_cg(cproc, &op->cut_cg);
if (!build_draw_cut_cg(cproc, &op->cut_cg))
return false;
break;
case PARTS_CP_COPY_CUT_CG:
build_copy_cut_cg(cproc, &op->cut_cg);
if (!build_copy_cut_cg(cproc, &op->cut_cg))
return false;
break;
case PARTS_CP_DRAW_TEXT:
build_draw_text(cproc, &op->text);
if (!build_draw_text(cproc, &op->text))
return false;
break;
case PARTS_CP_COPY_TEXT:
build_copy_text(cproc, &op->text);
if (!build_copy_text(cproc, &op->text))
return false;
break;
case PARTS_CP_GRAY_FILTER:
if (!build_gray_filter(cproc, &op->filter))
return false;
break;
}
}
+72 -4
View File
@@ -110,11 +110,13 @@ static void parts_construction_process_to_json(struct parts_construction_process
[PARTS_CP_FILL] = "fill",
[PARTS_CP_FILL_ALPHA_COLOR] = "fill_alpha_color",
[PARTS_CP_FILL_AMAP] = "fill_amap",
[PARTS_CP_FILL_WITH_ALPHA] = "fill_with_alpha",
[PARTS_CP_DRAW_RECT] = "draw_rect",
[PARTS_CP_DRAW_CUT_CG] = "draw_cut_cg",
[PARTS_CP_COPY_CUT_CG] = "copy_cut_cg",
[PARTS_CP_DRAW_TEXT] = "draw_text",
[PARTS_CP_COPY_TEXT] = "copy_text",
[PARTS_CP_GRAY_FILTER] = "gray_filter",
};
cJSON *ops, *tmp;
@@ -138,11 +140,12 @@ static void parts_construction_process_to_json(struct parts_construction_process
cJSON_AddNumberToObject(tmp, "h", op->create.h);
break;
case PARTS_CP_CG:
cJSON_AddNumberToObject(obj, "no", op->cg.no);
cJSON_AddSjisToObject(obj, "name", op->cg.name->text);
break;
case PARTS_CP_FILL:
case PARTS_CP_FILL_ALPHA_COLOR:
case PARTS_CP_FILL_AMAP:
case PARTS_CP_FILL_WITH_ALPHA:
case PARTS_CP_DRAW_RECT:
cJSON_AddItemToObjectCS(obj, "rect", tmp = cJSON_CreateObject());
cJSON_AddNumberToObject(tmp, "x", op->fill.x);
@@ -157,7 +160,7 @@ static void parts_construction_process_to_json(struct parts_construction_process
break;
case PARTS_CP_DRAW_CUT_CG:
case PARTS_CP_COPY_CUT_CG:
cJSON_AddNumberToObject(obj, "no", op->cut_cg.cg_no);
cJSON_AddSjisToObject(obj, "name", op->cut_cg.cg_name->text);
cJSON_AddItemToObjectCS(obj, "dst", tmp = cJSON_CreateObject());
cJSON_AddNumberToObject(tmp, "x", op->cut_cg.dx);
cJSON_AddNumberToObject(tmp, "y", op->cut_cg.dy);
@@ -179,6 +182,13 @@ static void parts_construction_process_to_json(struct parts_construction_process
cJSON_AddNumberToObject(obj, "line_space", op->text.line_space);
cJSON_AddItemToObjectCS(obj, "style", text_style_to_json(&op->text.style, verbose));
break;
case PARTS_CP_GRAY_FILTER:
cJSON_AddNumberToObject(obj, "x", op->filter.x);
cJSON_AddNumberToObject(obj, "y", op->filter.y);
cJSON_AddNumberToObject(obj, "w", op->filter.w);
cJSON_AddNumberToObject(obj, "h", op->filter.h);
cJSON_AddBoolToObject(obj, "full_size", op->filter.full_size);
break;
}
}
}
@@ -190,6 +200,20 @@ static void parts_flash_to_json(struct parts_flash *flash, cJSON *out, bool verb
cJSON_AddNumberToObject(out, "current_frame", flash->current_frame);
}
static void parts_flat_to_json(struct parts_flat *flat, cJSON *out, bool verbose)
{
if (flat->name)
cJSON_AddSjisToObject(out, "name", flat->name->text);
if (flat->root_state)
cJSON_AddNumberToObject(out, "current_frame", flat->root_state->current_frame);
cJSON_AddNumberToObject(out, "end_frame", flat->end_frame);
}
static void parts_movie_to_json(struct parts_movie *movie, cJSON *out, bool verbose)
{
cJSON_AddNumberToObject(out, "sprite_no", movie->sprite_no);
}
static cJSON *parts_state_to_json(struct parts_state *state, bool verbose)
{
static const char *state_types[PARTS_NR_TYPES] = {
@@ -201,7 +225,8 @@ static cJSON *parts_state_to_json(struct parts_state *state, bool verbose)
[PARTS_HGAUGE] = "hgauge",
[PARTS_VGAUGE] = "vgauge",
[PARTS_CONSTRUCTION_PROCESS] = "construction_process",
[PARTS_FLASH] = "flash"
[PARTS_FLASH] = "flash",
[PARTS_FLAT] = "flat"
};
const char *type = "invalid";
if (state->type >= 0 && state->type < PARTS_NR_TYPES)
@@ -239,6 +264,22 @@ static cJSON *parts_state_to_json(struct parts_state *state, bool verbose)
case PARTS_FLASH:
parts_flash_to_json(&state->flash, obj, verbose);
break;
case PARTS_FLAT:
parts_flat_to_json(&state->flat, obj, verbose);
break;
case PARTS_MOVIE:
parts_movie_to_json(&state->movie, obj, verbose);
break;
case PARTS_RECT_DETECTION:
break;
case PARTS_LAYOUT_BOX:
cJSON_AddNumberToObject(obj, "layout_type", state->layout_box.layout_type);
cJSON_AddNumberToObject(obj, "align", state->layout_box.align);
break;
case PARTS_3DLAYER:
cJSON_AddNumberToObject(obj, "plugin", state->layer3d.plugin);
cJSON_AddNumberToObject(obj, "sprite_no", state->layer3d.sprite_no);
break;
}
return obj;
@@ -262,6 +303,7 @@ static cJSON *parts_params_to_json(struct parts_params *params, bool verbose)
cJSON_AddNumberToObject(tmp, "z", params->rotation.z);
cJSON_AddItemToObjectCS(obj, "add_color", color_to_json(&params->add_color, verbose));
cJSON_AddItemToObjectCS(obj, "mul_color", color_to_json(&params->multiply_color, verbose));
cJSON_AddNumberToObject(obj, "alpha_clipper_parts_no", params->alpha_clipper_parts_no);
return obj;
}
@@ -349,10 +391,15 @@ cJSON *parts_to_json(struct parts *parts, bool recursive, bool verbose)
cJSON_AddItemToObjectCS(obj, "clicked", parts_state_to_json(&parts->states[PARTS_STATE_CLICKED], verbose));
cJSON_AddItemToObjectCS(obj, "local", parts_params_to_json(&parts->local, verbose));
cJSON_AddItemToObjectCS(obj, "global", parts_params_to_json(&parts->global, verbose));
if (parts_multi_controller)
cJSON_AddNumberToObject(obj, "controller", parts->controller_no);
if (parts->delegate_index >= 0)
cJSON_AddNumberToObject(obj, "delegate_index", parts->delegate_index);
cJSON_AddNumberToObject(obj, "sprite_deform", parts->sprite_deform);
cJSON_AddBoolToObject(obj, "clickable", parts->clickable);
cJSON_AddBoolToObject(obj, "pass_cursor", parts->pass_cursor);
cJSON_AddBoolToObject(obj, "lock_input_state", parts->lock_input_state);
cJSON_AddBoolToObject(obj, "want_save", parts->want_save);
if (parts->on_cursor_sound >= 0)
cJSON_AddNumberToObject(obj, "on_cursor_sound", parts->on_cursor_sound);
if (parts->on_click_sound >= 0)
@@ -489,6 +536,9 @@ static void parts_list_print(struct parts *parts, int indent)
case PARTS_CP_FILL_AMAP:
sys_message(" fill-amap");
break;
case PARTS_CP_FILL_WITH_ALPHA:
sys_message(" fill-with-alpha");
break;
case PARTS_CP_DRAW_RECT:
sys_message(" draw-rect");
break;
@@ -504,6 +554,9 @@ static void parts_list_print(struct parts *parts, int indent)
case PARTS_CP_COPY_TEXT:
sys_message(" copy-text");
break;
case PARTS_CP_GRAY_FILTER:
sys_message(" gray-filter");
break;
}
}
sys_message(")");
@@ -512,9 +565,24 @@ static void parts_list_print(struct parts *parts, int indent)
case PARTS_FLASH:
sys_message("(flash %s)", display_sjis0(state->flash.name->text));
break;
case PARTS_FLAT:
sys_message("(flat %s)", state->flat.name ? display_sjis0(state->flat.name->text) : "(null)");
break;
case PARTS_MOVIE:
sys_message("(movie sp=%d)", state->movie.sprite_no);
break;
case PARTS_RECT_DETECTION:
sys_message("(rect_detection)");
break;
case PARTS_LAYOUT_BOX:
sys_message("(layout_box type=%d)", state->layout_box.layout_type);
break;
case PARTS_3DLAYER:
sys_message("(3dlayer plugin=%d sp=%d)", state->layer3d.plugin, state->layer3d.sprite_no);
break;
}
sys_message(" @ z=%d (%d)\n", parts->global.z, parts->local.z);
sys_message(" @ controller=%d z=%d (%d)\n", parts->controller_no, parts->global.z, parts->local.z);
struct parts *child;
PARTS_FOREACH_CHILD(child, parts) {
+17 -10
View File
@@ -38,7 +38,7 @@ static void clear_display_list(struct parts_flash *f)
while (!TAILQ_EMPTY(&f->display_list)) {
struct parts_flash_object *obj = TAILQ_FIRST(&f->display_list);
TAILQ_REMOVE(&f->display_list, obj, entry);
free(obj);
xfree_aligned(obj);
}
}
@@ -62,7 +62,7 @@ void parts_flash_free(struct parts_flash *f)
ht_free_int(f->bitmaps);
}
if (f->sprites) {
ht_foreach_value(f->sprites, free);
ht_foreach_value(f->sprites, xfree_aligned);
ht_free_int(f->sprites);
}
}
@@ -88,6 +88,7 @@ bool parts_flash_load(struct parts *parts, struct parts_flash *f, struct string
f->name = string_dup(filename);
f->tag = f->swf->tags;
f->has_ended = false;
f->stopped = false;
f->elapsed = 0;
f->current_frame = 0;
@@ -128,10 +129,10 @@ static int do_action(struct parts_flash *f, struct swf_tag_do_action *tag)
for (struct swf_action *a = tag->actions; a->code != ACTION_END; a = a->next) {
switch (a->code) {
case ACTION_PLAY:
f->stopped = false;
f->has_ended = false;
break;
case ACTION_STOP:
f->stopped = true;
f->has_ended = true;
break;
case ACTION_GOTO_FRAME:
seek_frame = ((struct swf_action_goto_frame*)a)->frame;
@@ -153,7 +154,7 @@ static struct parts_flash_object *update_object(struct parts_flash_object *obj,
{
if (!obj) {
if (tag->flags & PLACE_FLAG_HAS_CHARACTER) {
obj = xcalloc(1, sizeof(struct parts_flash_object));
obj = xcalloc_aligned(1, struct parts_flash_object);
obj->depth = tag->depth;
} else {
ERROR("no object to update (depth=%d)", tag->depth);
@@ -216,7 +217,7 @@ static void remove_object(struct parts_flash *f, struct swf_tag_remove_object2 *
TAILQ_FOREACH(p, &f->display_list, entry) {
if (p->depth == tag->depth) {
TAILQ_REMOVE(&f->display_list, p, entry);
free(p);
xfree_aligned(p);
return;
}
}
@@ -304,8 +305,6 @@ static void define_sprite(struct parts_flash *f, struct swf_tag_define_sprite *t
ERROR("unsupported tag %d in sprite", t->type);
}
}
if (!obj)
ERROR("sprite has no PlaceObject2 tag");
slot->value = obj;
}
@@ -330,7 +329,7 @@ bool parts_flash_seek(struct parts_flash *f, int frame)
// Do nothing.
break;
case TAG_END:
f->stopped = true;
f->has_ended = true;
break;
case TAG_SHOW_FRAME:
f->current_frame++;
@@ -373,7 +372,7 @@ bool parts_flash_seek(struct parts_flash *f, int frame)
bool parts_flash_update(struct parts_flash *f, int passed_time)
{
if (!f->swf || f->stopped)
if (!f->swf || f->has_ended || f->stopped)
return false;
f->elapsed += passed_time;
int delta_frame = muldiv(f->elapsed, f->swf->frame_rate, 256000);
@@ -383,6 +382,13 @@ bool parts_flash_update(struct parts_flash *f, int passed_time)
return parts_flash_seek(f, f->current_frame + delta_frame);
}
bool PE_ExistsFlashFile(struct string *flash_filename)
{
if (!flash_filename)
return false;
return asset_exists_by_name(ASSET_FLASH, flash_filename->text, NULL);
}
bool PE_SetPartsFlash(int parts_no, struct string *flash_filename, int state)
{
if (!parts_state_valid(--state))
@@ -427,6 +433,7 @@ bool PE_BackPartsFlashBeginFrame(int parts_no, int state)
struct parts_flash *f = parts_get_flash(parts, state);
if (!f->swf)
return false;
f->has_ended = false;
if (parts_flash_seek(f, 1))
parts_dirty(parts);
f->elapsed = 0;
+1044
View File
File diff suppressed because it is too large Load Diff
+181 -22
View File
@@ -22,11 +22,11 @@
#include "xsystem4.h"
#include "parts_internal.h"
// the mouse position at last update
Point parts_prev_pos = {0};
// true between calls to BeginClick and EndClick
bool parts_began_click = false;
// the mouse position at last update
static Point parts_prev_pos = {0};
// true when the left mouse button was down last update
static bool prev_clicking = false;
// the last (fully) clicked parts number
@@ -34,57 +34,205 @@ static int clicked_parts = 0;
// the current (partially) clicked parts number
static int click_down_parts = 0;
static void parts_update_mouse(struct parts *parts, Point cur_pos, bool cur_clicking)
{
Rectangle *hitbox = &parts->states[parts->state].common.hitbox;
bool prev_in = SDL_PointInRect(&parts_prev_pos, hitbox);
bool cur_in = SDL_PointInRect(&cur_pos, hitbox);
// drag state
static struct parts *drag_parts = NULL;
static bool is_dragging = false;
static Point drag_initial_pos;
static Point drag_start_cursor;
static struct parts *drop_target = NULL;
if (parts->linked_from >= 0 && cur_in != prev_in) {
static bool parts_hittest(struct parts *parts, int state, Point pos)
{
Rectangle hitbox = parts->states[state].common.hitbox;
if (parts->parent) {
hitbox.x += parts->parent->global.pos.x;
hitbox.y += parts->parent->global.pos.y;
}
return SDL_PointInRect(&pos, &hitbox);
}
static void drag_state_reset(void)
{
drag_parts = NULL;
is_dragging = false;
drop_target = NULL;
}
void parts_input_reset_drag(struct parts *parts)
{
if (parts == drag_parts)
drag_state_reset();
else if (parts == drop_target)
drop_target = NULL;
}
static void parts_update_mouse(struct parts *parts, Point cur_pos, bool cur_clicking,
int passed_time, bool *hover_consumed, bool *click_consumed)
{
// Always use DEFAULT state hitbox regardless of the current display state.
bool is_hovered = parts_hittest(parts, PARTS_STATE_DEFAULT, cur_pos)
&& !*hover_consumed;
bool was_hovered = parts->is_hovered;
parts->is_hovered = is_hovered;
// !pass_cursor parts consume the cursor for parts behind them
if (is_hovered && !parts->pass_cursor)
*hover_consumed = true;
if (parts->linked_from >= 0 && is_hovered != was_hovered) {
parts_dirty(parts_get(parts->linked_from));
}
if (!parts_began_click || !parts->clickable)
if (!parts_began_click)
return;
if (!cur_in) {
if (is_hovered && !was_hovered) {
parts_msg_push(parts, PARTS_MSG_MOUSE_ENTER, "ii", cur_pos.x, cur_pos.y);
parts->hover_time = 0;
}
if (!is_hovered && was_hovered) {
parts_msg_push(parts, PARTS_MSG_MOUSE_LEAVE, "ii", cur_pos.x, cur_pos.y);
}
if (is_hovered) {
// MOUSE_ON message fires every frame while hovering
parts_msg_push(parts, PARTS_MSG_MOUSE_ON,
"iii", cur_pos.x, cur_pos.y, parts->hover_time);
parts->hover_time += passed_time;
// MOUSE_MOVE message fires when cursor moves while hovering
if (cur_pos.x != parts_prev_pos.x || cur_pos.y != parts_prev_pos.y) {
parts_msg_push(parts, PARTS_MSG_MOUSE_MOVE, "ii", cur_pos.x, cur_pos.y);
}
}
if (!is_hovered) {
parts_set_state(parts, PARTS_STATE_DEFAULT);
return;
}
// click down: just remember the parts number for later
bool click_eligible = parts->clickable || !parts->pass_cursor;
if (!click_eligible || *click_consumed) {
if (!was_hovered)
audio_play_sound(parts->on_cursor_sound);
parts_set_state(parts, PARTS_STATE_HOVERED);
return;
}
// click down: first eligible part captures the click
if (cur_clicking && !prev_clicking) {
click_down_parts = parts->no;
*click_consumed = true;
drag_parts = parts;
drag_initial_pos = parts->local.pos;
drag_start_cursor = cur_pos;
// KEY_TRIGGER message fires on press transition
parts_msg_push(parts, PARTS_MSG_KEY_TRIGGER, "i", VK_LBUTTON);
}
// KEY_DOWN message fires every frame while held (not first frame)
if (prev_clicking && cur_clicking && click_down_parts == parts->no) {
parts_msg_push(parts, PARTS_MSG_KEY_DOWN, "i", VK_LBUTTON);
}
if (cur_clicking && click_down_parts == parts->no) {
parts_set_state(parts, PARTS_STATE_CLICKED);
} else {
if (!prev_in) {
if (!was_hovered) {
audio_play_sound(parts->on_cursor_sound);
}
parts_set_state(parts, PARTS_STATE_HOVERED);
}
// click event: only if the click down event had same parts number
if (prev_clicking && !cur_clicking && click_down_parts == parts->no) {
if (parts->clickable && prev_clicking && !cur_clicking
&& click_down_parts == parts->no) {
audio_play_sound(parts->on_click_sound);
clicked_parts = parts->no;
parts_msg_push(parts, PARTS_MSG_MOUSE_CLICK,
"iii", cur_pos.x, cur_pos.y, VK_LBUTTON);
parts_msg_push(parts, PARTS_MSG_KEY_UP, "i", VK_LBUTTON);
}
}
void PE_UpdateInputState(possibly_unused int passed_time)
void PE_UpdateInputState(int passed_time)
{
Point cur_pos;
bool cur_clicking = key_is_down(VK_LBUTTON);
mouse_get_pos(&cur_pos.x, &cur_pos.y);
bool hover_consumed = false;
bool click_consumed = false;
struct parts *parts;
PARTS_LIST_FOREACH(parts) {
parts_update_mouse(parts, cur_pos, cur_clicking);
// Iterate front-to-back (highest z first) for proper cursor consumption
PARTS_LIST_FOREACH_REVERSE(parts) {
parts_update_mouse(parts, cur_pos, cur_clicking, passed_time,
&hover_consumed, &click_consumed);
}
// Drag movement processing
if (drag_parts && cur_clicking) {
bool cursor_moved = (cur_pos.x != parts_prev_pos.x ||
cur_pos.y != parts_prev_pos.y);
if (cursor_moved && drag_parts->draggable) {
Point new_pos = {
drag_initial_pos.x + (cur_pos.x - drag_start_cursor.x),
drag_initial_pos.y + (cur_pos.y - drag_start_cursor.y)
};
parts_set_pos(drag_parts, new_pos);
if (!is_dragging) {
parts_msg_push(drag_parts, PARTS_MSG_DRAG_BEGIN, "");
}
parts_msg_push(drag_parts, PARTS_MSG_DRAGGING, "iiii",
drag_start_cursor.x, drag_start_cursor.y, cur_pos.x, cur_pos.y);
is_dragging = true;
}
// Drop target tracking
if (cursor_moved && is_dragging) {
struct parts *new_drop = NULL;
PARTS_LIST_FOREACH_REVERSE(parts) {
if (parts == drag_parts)
continue;
if (parts_hittest(parts, PARTS_STATE_DEFAULT, cur_pos)) {
new_drop = parts;
if (!parts->pass_cursor)
break;
}
}
if (new_drop != drop_target) {
if (drop_target) {
parts_msg_push(drop_target, PARTS_MSG_DROP_LEAVE,
"i", drag_parts->no);
}
if (new_drop) {
parts_msg_push(new_drop, PARTS_MSG_DROP_ENTER,
"i", drag_parts->no);
}
drop_target = new_drop;
} else if (drop_target) {
parts_msg_push(drop_target, PARTS_MSG_DROP_ON,
"iii", drag_parts->no, cur_pos.x, cur_pos.y);
}
}
}
// Release handling
if (prev_clicking && !cur_clicking) {
if (is_dragging && drag_parts && drag_parts->draggable) {
parts_msg_push(drag_parts, PARTS_MSG_DRAG_END, "");
}
if (drop_target && drag_parts) {
parts_msg_push(drop_target, PARTS_MSG_DROPPED,
"iii", drag_parts->no, cur_pos.x, cur_pos.y);
parts_msg_push(drop_target, PARTS_MSG_DROP_LEAVE, "i", drag_parts->no);
}
drag_state_reset();
if (!click_down_parts) {
// TODO: play misclick sound
}
@@ -95,6 +243,16 @@ void PE_UpdateInputState(possibly_unused int passed_time)
parts_prev_pos = cur_pos;
}
void PE_SetPassCursor(int parts_no, bool pass)
{
parts_get(parts_no)->pass_cursor = !!pass;
}
bool PE_GetPartsPassCursor(int parts_no)
{
return parts_get(parts_no)->pass_cursor;
}
void PE_SetClickable(int parts_no, bool clickable)
{
parts_get(parts_no)->clickable = !!clickable;
@@ -167,6 +325,12 @@ void PE_EndInput(void)
{
parts_began_click = false;
clicked_parts = 0;
drag_state_reset();
}
void PE_SetDrag(int parts_no, bool enable)
{
parts_get(parts_no)->draggable = !!enable;
}
int PE_GetClickPartsNumber(void)
@@ -183,11 +347,6 @@ bool PE_IsCursorIn(int parts_no, int mouse_x, int mouse_y, int state)
if (!parts)
return false;
Rectangle hitbox = parts->states[state].common.hitbox;
if (parts->parent) {
hitbox.x += parts->parent->global.pos.x;
hitbox.y += parts->parent->global.pos.y;
}
Point mouse_pos = { mouse_x, mouse_y };
return SDL_PointInRect(&mouse_pos, &hitbox);
return parts_hittest(parts, state, mouse_pos);
}
+337
View File
@@ -0,0 +1,337 @@
/* Copyright (C) 2026 kichikuou <KichikuouChrome@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://gnu.org/licenses/>.
*/
#include <stdbool.h>
#include <stdlib.h>
#include "system4/flat.h"
#include "parts.h"
#include "parts_internal.h"
void PE_SetComponentMargin(int parts_no, int top, int bottom, int left, int right)
{
struct parts *parts = parts_get(parts_no);
parts->margin_top = top;
parts->margin_bottom = bottom;
parts->margin_left = left;
parts->margin_right = right;
}
int PE_GetComponentMarginTop(int parts_no)
{
return parts_get(parts_no)->margin_top;
}
int PE_GetComponentMarginBottom(int parts_no)
{
return parts_get(parts_no)->margin_bottom;
}
int PE_GetComponentMarginLeft(int parts_no)
{
return parts_get(parts_no)->margin_left;
}
int PE_GetComponentMarginRight(int parts_no)
{
return parts_get(parts_no)->margin_right;
}
void PE_SetLayoutBoxLayoutType(int parts_no, int type)
{
struct parts *parts = parts_get(parts_no);
struct parts_layout_box *lb = parts_get_layout_box(parts);
if (lb->layout_type != type) {
lb->layout_type = type;
parts_component_dirty(parts);
}
}
int PE_GetLayoutBoxLayoutType(int parts_no)
{
struct parts *parts = parts_try_get(parts_no);
if (!parts || parts->states[0].type != PARTS_LAYOUT_BOX)
return PARTS_LAYOUT_FREE;
return parts->states[0].layout_box.layout_type;
}
void PE_SetLayoutBoxReturn(int parts_no, bool return_flag, int return_size)
{
struct parts *parts = parts_get(parts_no);
struct parts_layout_box *lb = parts_get_layout_box(parts);
lb->wrap = return_flag;
lb->wrap_size = return_size;
parts_component_dirty(parts);
}
bool PE_IsLayoutBoxReturn(int parts_no)
{
struct parts *parts = parts_try_get(parts_no);
if (!parts || parts->states[0].type != PARTS_LAYOUT_BOX)
return false;
return parts->states[0].layout_box.wrap;
}
int PE_GetLayoutBoxReturnSize(int parts_no)
{
struct parts *parts = parts_try_get(parts_no);
if (!parts || parts->states[0].type != PARTS_LAYOUT_BOX)
return 0;
return parts->states[0].layout_box.wrap_size;
}
void PE_SetLayoutBoxAlign(int parts_no, int align)
{
struct parts *parts = parts_get(parts_no);
struct parts_layout_box *lb = parts_get_layout_box(parts);
if (lb->align != align) {
lb->align = align;
parts_component_dirty(parts);
}
}
int PE_GetLayoutBoxAlign(int parts_no)
{
struct parts *parts = parts_try_get(parts_no);
if (!parts || parts->states[0].type != PARTS_LAYOUT_BOX)
return 0;
return parts->states[0].layout_box.align;
}
void PE_set_layoutbox_padding(int parts_no, int top, int bottom, int left, int right)
{
struct parts *parts = parts_get(parts_no);
struct parts_layout_box *lb = parts_get_layout_box(parts);
lb->padding_top = top;
lb->padding_bottom = bottom;
lb->padding_left = left;
lb->padding_right = right;
parts_component_dirty(parts);
}
int PE_get_layoutbox_padding_top(int parts_no)
{
struct parts *parts = parts_try_get(parts_no);
if (!parts || parts->states[0].type != PARTS_LAYOUT_BOX)
return 0;
return parts->states[0].layout_box.padding_top;
}
int PE_get_layoutbox_padding_bottom(int parts_no)
{
struct parts *parts = parts_try_get(parts_no);
if (!parts || parts->states[0].type != PARTS_LAYOUT_BOX)
return 0;
return parts->states[0].layout_box.padding_bottom;
}
int PE_get_layoutbox_padding_left(int parts_no)
{
struct parts *parts = parts_try_get(parts_no);
if (!parts || parts->states[0].type != PARTS_LAYOUT_BOX)
return 0;
return parts->states[0].layout_box.padding_left;
}
int PE_get_layoutbox_padding_right(int parts_no)
{
struct parts *parts = parts_try_get(parts_no);
if (!parts || parts->states[0].type != PARTS_LAYOUT_BOX)
return 0;
return parts->states[0].layout_box.padding_right;
}
static void parts_get_layout_size(struct parts *parts, int *w, int *h)
{
struct parts_state *state = &parts->states[parts->state];
if (state->type == PARTS_FLAT && state->flat.flat) {
// The layout size of Flat is specified in the header, and this may
// differ from the hitbox size.
*w = state->flat.flat->hdr.width;
*h = state->flat.flat->hdr.height;
} else {
*w = state->common.w;
*h = state->common.h;
}
}
static int align_offset_x(int align, int total_w)
{
switch (align) {
case 2: case 5: case 8: return -(total_w / 2);
case 3: case 6: case 9: return -total_w;
default: return 0;
}
}
static int align_offset_y(int align, int total_h)
{
switch (align) {
case 4: case 5: case 6: return -(total_h / 2);
case 7: case 8: case 9: return -total_h;
default: return 0;
}
}
// Calculates the total size of a horizontal layout box by summing child widths per row.
static void parts_layout_calc_size_horizontal(struct parts_layout_box *lb, struct parts *parts,
int *out_w, int *out_h)
{
int row_w = 0, max_row_w = 0, max_row_h = 0, total_h = 0;
struct parts *child;
PARTS_FOREACH_CHILD(child, parts) {
int child_w, child_h;
parts_get_layout_size(child, &child_w, &child_h);
int cw = child->margin_left + child_w + child->margin_right;
int ch = child->margin_top + child_h + child->margin_bottom;
if (lb->wrap && row_w + cw > lb->wrap_size && row_w > 0) {
if (row_w > max_row_w)
max_row_w = row_w;
total_h += max_row_h;
row_w = 0;
max_row_h = 0;
}
row_w += cw;
if (ch > max_row_h)
max_row_h = ch;
}
if (row_w > max_row_w)
max_row_w = row_w;
total_h += max_row_h;
*out_w = max_row_w + lb->padding_left + lb->padding_right;
*out_h = total_h + lb->padding_top + lb->padding_bottom;
}
// Calculates the total size of a vertical layout box by summing child heights per column.
static void parts_layout_calc_size_vertical(struct parts_layout_box *lb, struct parts *parts,
int *out_w, int *out_h)
{
int col_h = 0, max_col_h = 0, max_col_w = 0, total_w = 0;
struct parts *child;
PARTS_FOREACH_CHILD(child, parts) {
int child_w, child_h;
parts_get_layout_size(child, &child_w, &child_h);
int cw = child->margin_left + child_w + child->margin_right;
int ch = child->margin_top + child_h + child->margin_bottom;
if (lb->wrap && col_h + ch > lb->wrap_size && col_h > 0) {
if (col_h > max_col_h)
max_col_h = col_h;
total_w += max_col_w;
col_h = 0;
max_col_w = 0;
}
col_h += ch;
if (cw > max_col_w)
max_col_w = cw;
}
if (col_h > max_col_h)
max_col_h = col_h;
total_w += max_col_w;
*out_w = total_w + lb->padding_left + lb->padding_right;
*out_h = max_col_h + lb->padding_top + lb->padding_bottom;
}
void parts_do_layout(struct parts *parts)
{
if (parts->states[0].type != PARTS_LAYOUT_BOX)
return;
struct parts_layout_box *lb = &parts->states[0].layout_box;
if (lb->layout_type == PARTS_LAYOUT_FREE)
return;
int total_w, total_h;
if (lb->layout_type == PARTS_LAYOUT_HORIZONTAL)
parts_layout_calc_size_horizontal(lb, parts, &total_w, &total_h);
else
parts_layout_calc_size_vertical(lb, parts, &total_w, &total_h);
int align = lb->align;
bool is_left = (align == 1 || align == 4 || align == 7);
bool is_right = (align == 3 || align == 6 || align == 9);
bool is_top = (align == 1 || align == 2 || align == 3);
bool is_bottom = (align == 7 || align == 8 || align == 9);
int origin_x = align_offset_x(parts->origin_mode, total_w);
int origin_y = align_offset_y(parts->origin_mode, total_h);
int align_x = align_offset_x(align, total_w);
int align_y = align_offset_y(align, total_h);
int start_x = (is_left ? lb->padding_left : -lb->padding_right)
+ (origin_x - align_x);
int start_y = (is_top ? lb->padding_top : -lb->padding_bottom)
+ (origin_y - align_y);
int x_dir = is_right ? -1 : 1;
int y_dir = is_bottom ? -1 : 1;
int cur_x = start_x;
int cur_y = start_y;
int max_cross = 0;
struct parts *child;
PARTS_FOREACH_CHILD(child, parts) {
int child_w, child_h;
parts_get_layout_size(child, &child_w, &child_h);
int cw = child->margin_left + child_w + child->margin_right;
int ch = child->margin_top + child_h + child->margin_bottom;
// The layout box forces the child's origin_mode to match its own align.
if (child->origin_mode != align) {
child->origin_mode = align;
parts_recalculate_hitbox(child);
}
if (lb->layout_type == PARTS_LAYOUT_HORIZONTAL) {
if (lb->wrap && abs((cur_x + cw * x_dir) - start_x) > lb->wrap_size
&& max_cross > 0) {
cur_y += max_cross * y_dir;
max_cross = 0;
cur_x = start_x;
}
} else {
if (lb->wrap && abs((cur_y + ch * y_dir) - start_y) > lb->wrap_size
&& max_cross > 0) {
cur_x += max_cross * x_dir;
max_cross = 0;
cur_y = start_y;
}
}
// margin offset depends on alignment direction
int mx = is_left ? child->margin_left : is_right ? -child->margin_right : 0;
int my = is_top ? child->margin_top : is_bottom ? -child->margin_bottom : 0;
int pos_x = cur_x + mx;
int pos_y = cur_y + my;
if (child->local.pos.x != pos_x || child->local.pos.y != pos_y) {
child->local.pos.x = pos_x;
child->local.pos.y = pos_y;
parts_recalculate_hitbox(child);
parts_component_dirty(child);
}
if (lb->layout_type == PARTS_LAYOUT_HORIZONTAL) {
cur_x += cw * x_dir;
if (ch > max_cross)
max_cross = ch;
} else {
cur_y += ch * y_dir;
if (cw > max_cross)
max_cross = cw;
}
}
}
+146
View File
@@ -0,0 +1,146 @@
/* Copyright (C) 2026 kichikuou <KichikuouChrome@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://gnu.org/licenses/>.
*/
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include "system4.h"
#include "system4/string.h"
#include "vm.h"
#include "parts.h"
#include "parts_internal.h"
#define PARTS_MSG_MAX_VARS 4
struct parts_message {
STAILQ_ENTRY(parts_message) entry;
int parts_no;
int delegate_index;
int type;
int variables[PARTS_MSG_MAX_VARS];
int nr_variables;
};
static STAILQ_HEAD(, parts_message) msg_queue =
STAILQ_HEAD_INITIALIZER(msg_queue);
void parts_msg_push(struct parts* parts, int type, const char *fmt, ...)
{
// Message system is introduced in Rance 9
if (!parts_multi_controller)
return;
struct parts_message *msg = xmalloc(sizeof(*msg));
msg->parts_no = parts->no;
msg->delegate_index = parts->delegate_index;
msg->type = type;
msg->nr_variables = 0;
va_list ap;
va_start(ap, fmt);
for (const char *p = fmt; *p; p++) {
if (msg->nr_variables >= PARTS_MSG_MAX_VARS) {
VM_ERROR("parts_msg_push: too many variables");
}
switch (*p) {
case 'i':
msg->variables[msg->nr_variables++] = va_arg(ap, int);
break;
default:
VM_ERROR("parts_msg_push: unsupported format '%c'", *p);
}
}
va_end(ap);
STAILQ_INSERT_TAIL(&msg_queue, msg, entry);
}
void PE_ReleaseMessage(void)
{
while (!STAILQ_EMPTY(&msg_queue)) {
struct parts_message *msg = STAILQ_FIRST(&msg_queue);
STAILQ_REMOVE_HEAD(&msg_queue, entry);
free(msg);
}
}
void PE_PopMessage(void)
{
if (STAILQ_EMPTY(&msg_queue))
return;
struct parts_message *msg = STAILQ_FIRST(&msg_queue);
STAILQ_REMOVE_HEAD(&msg_queue, entry);
free(msg);
}
int PE_GetMessageType(void)
{
struct parts_message *msg = STAILQ_FIRST(&msg_queue);
return msg ? msg->type : 0;
}
int PE_GetMessagePartsNumber(void)
{
struct parts_message *msg = STAILQ_FIRST(&msg_queue);
return msg ? msg->parts_no : 0;
}
int PE_GetMessageDelegateIndex(void)
{
struct parts_message *msg = STAILQ_FIRST(&msg_queue);
return msg ? msg->delegate_index : -1;
}
int PE_GetMessageVariableCount(void)
{
struct parts_message *msg = STAILQ_FIRST(&msg_queue);
return msg ? msg->nr_variables : 0;
}
int PE_GetMessageVariableType(possibly_unused int index)
{
// In the current implementation, all variables are integers.
return 1;
}
int PE_GetMessageVariableInt(int index)
{
struct parts_message *msg = STAILQ_FIRST(&msg_queue);
if (!msg)
return 0;
if (index < 0 || index >= msg->nr_variables)
return 0;
return msg->variables[index];
}
float PE_GetMessageVariableFloat(possibly_unused int index)
{
return 0.0f;
}
bool PE_GetMessageVariableBool(possibly_unused int index)
{
return false;
}
void PE_GetMessageVariableString(possibly_unused int index, struct string **out)
{
if (*out)
free_string(*out);
*out = string_ref(&EMPTY_STRING);
}
+48 -6
View File
@@ -38,6 +38,8 @@ static struct parts_motion_list global_motion_list =
// true after call to BeginMotion until the motion ends or EndMotion is called
static bool is_motion = false;
// true while the motion is paused (time does not advance)
static bool is_motion_paused = false;
// the elapsed time of the current motion
static int motion_t = 0;
// the end time of the current motion
@@ -154,10 +156,15 @@ static void parts_update_with_motion(struct parts *parts, struct parts_motion *m
case PARTS_MOTION_ALPHA:
parts_set_alpha(parts, motion_calculate_i(motion, motion_t));
break;
case PARTS_MOTION_CG:
parts_cg_set_by_index(parts, parts_get_cg(parts, parts->state),
motion_calculate_i(motion, motion_t));
case PARTS_MOTION_CG: {
int i = motion_calculate_i(motion, motion_t);
if (i == 0) {
parts_state_reset(&parts->states[parts->state], PARTS_CG);
} else {
parts_cg_set_by_index(parts, parts_get_cg(parts, parts->state), i);
}
break;
}
case PARTS_MOTION_HGAUGE_RATE:
parts_hgauge_set_rate(parts, parts_get_hgauge(parts, parts->state),
motion_calculate_f(motion, motion_t));
@@ -498,6 +505,7 @@ void PE_BeginMotion(void)
// FIXME: starting a motion seems to clear non-default states
motion_t = 0;
is_motion = true;
is_motion_paused = false;
parts_init_all_motion();
}
@@ -508,6 +516,7 @@ void PE_EndMotion(void)
motion_t = 0;
motion_end_t = 0;
is_motion = false;
is_motion_paused = false;
parts_fini_all_motion();
}
@@ -528,10 +537,26 @@ void PE_SeekEndMotion(void)
PE_SetMotionTime(motion_end_t - 1);
}
void PE_UpdateMotionTime(int time, possibly_unused bool skip)
static int msgskip_speedup_rate = 1;
void PE_UpdateMotionTime(int time, bool skip)
{
// TODO: use skip
PE_SetMotionTime(motion_t + time);
if (is_motion_paused)
return;
if (skip)
PE_SetMotionTime(motion_t + time * msgskip_speedup_rate);
else
PE_SetMotionTime(motion_t + time);
}
void PE_SetSpeedupRateByMessageSkip(int parts_no, int rate)
{
msgskip_speedup_rate = max(1, rate);
}
int PE_GetSpeedupRateByMessageSkip(int parts_no)
{
return msgskip_speedup_rate;
}
bool PE_IsMotion(void)
@@ -543,3 +568,20 @@ int PE_GetMotionEndTime(void)
{
return motion_end_t;
}
void PE_PauseMotion(bool pause)
{
if (parts_began_click)
return;
if (!is_motion)
return;
if (is_motion_paused == pause)
return;
is_motion_paused = pause;
// While paused, the per-frame advance stops updating the whole-screen
// vibration offset. Reset it to the origin so the screen doesn't stay
// stuck at the last shake position.
if (pause)
parts_set_global_pos((Point){ 0, 0 });
}
+554 -53
View File
@@ -26,25 +26,34 @@
#include "vm/page.h"
#include "xsystem4.h"
#include "sact.h"
#include "sprite.h"
#include "parts.h"
#include "parts_internal.h"
#include "reign.h"
struct parts_list parts_list = TAILQ_HEAD_INITIALIZER(parts_list);
static struct parts_list dirty_list = TAILQ_HEAD_INITIALIZER(dirty_list);
static struct hash_table *parts_table = NULL;
static Point root_pos = { 0, 0 };
#define PARTS_PARAMS_INITIALIZER (struct parts_params) { \
.z = 1, \
#define PARTS_PARAMS_INITIALIZER(Z) (struct parts_params) { \
.z = Z, \
.pos = { 0, 0 }, \
.show = true, \
.alpha = 255, \
.scale = { 1.0f, 1.0f }, \
.rotation = { 0.0f, 0.0f, 0.0f }, \
.add_color = { 0, 0, 0, 0 }, \
.multiply_color = { 255, 255, 255, 255 } \
.multiply_color = { 255, 255, 255, 255 }, \
.alpha_clipper_parts_no = 0 \
}
static struct parts_params root_params = PARTS_PARAMS_INITIALIZER(0);
struct parts_controller_stack ctrl_stack;
bool parts_multi_controller;
static void ctrl_stack_init(void);
static void parts_init(struct parts *parts)
{
parts->sp.z = 1;
@@ -52,9 +61,10 @@ static void parts_init(struct parts *parts)
parts->sp.has_alpha = true;
parts->sp.render = parts_sprite_render;
parts->sp.to_json = parts_sprite_to_json;
parts->local = PARTS_PARAMS_INITIALIZER;
parts->global = PARTS_PARAMS_INITIALIZER;
parts->local = PARTS_PARAMS_INITIALIZER(1);
parts->global = PARTS_PARAMS_INITIALIZER(1);
parts->delegate_index = -1;
parts->want_save = true;
parts->on_cursor_sound = -1;
parts->on_click_sound = -1;
parts->origin_mode = 1;
@@ -86,17 +96,38 @@ static void dirty_list_remove(struct parts *parts)
TAILQ_REMOVE(&dirty_list, parts, dirty_list_entry);
}
static int parts_get_sprite_z(struct parts *parts)
{
if (!parts_multi_controller)
return parts->global.z;
// The system overlay controller sorts above any in-stack controller.
return parts->controller_no;
}
static int parts_get_sprite_z2(struct parts *parts)
{
if (!parts_multi_controller)
return 0;
return parts->global.z;
}
static void parts_list_insert(struct parts *parts)
{
int z = parts_get_sprite_z(parts);
int z2 = parts_get_sprite_z2(parts);
parts->sp.z = z;
parts->sp.z2 = z2;
struct parts *p;
PARTS_LIST_FOREACH(p) {
if (p->global.z > parts->global.z) {
int pz = parts_get_sprite_z(p);
int pz2 = parts_get_sprite_z2(p);
if (pz > z || (pz == z && pz2 > z2)) {
TAILQ_INSERT_BEFORE(p, parts, parts_list_entry);
parts_engine_dirty();
return;
goto done;
}
}
TAILQ_INSERT_TAIL(&parts_list, parts, parts_list_entry);
done:
parts_engine_dirty();
scene_register_sprite(&parts->sp);
}
@@ -112,7 +143,6 @@ void parts_list_resort(struct parts *parts)
// TODO: this could be optimized
parts_list_remove(parts);
parts_list_insert(parts);
scene_set_sprite_z(&parts->sp, parts->global.z);
}
struct parts *parts_try_get(int parts_no)
@@ -131,6 +161,7 @@ struct parts *parts_get(int parts_no)
struct parts *parts = parts_alloc();
parts->no = parts_no;
parts->controller_no = ctrl_stack.active;
slot->value = parts;
parts_list_insert(parts);
return parts;
@@ -145,6 +176,8 @@ static void parts_state_free(struct parts_state *state)
{
switch (state->type) {
case PARTS_UNINITIALIZED:
case PARTS_RECT_DETECTION:
case PARTS_LAYOUT_BOX:
break;
case PARTS_CG:
gfx_delete_texture(&state->common.texture);
@@ -159,6 +192,8 @@ static void parts_state_free(struct parts_state *state)
gfx_delete_texture(&state->anim.frames[i]);
}
free(state->anim.frames);
if (state->anim.cg_name)
free_string(state->anim.cg_name);
break;
case PARTS_NUMERAL:
gfx_delete_texture(&state->common.texture);
@@ -175,6 +210,22 @@ static void parts_state_free(struct parts_state *state)
case PARTS_FLASH:
parts_flash_free(&state->flash);
break;
case PARTS_FLAT:
parts_flat_free(&state->flat);
break;
case PARTS_MOVIE:
// The texture is owned by the SACT sprite.
state->common.texture.handle = 0;
if (state->movie.sprite_no >= 0)
sact_SP_Delete(state->movie.sprite_no);
break;
case PARTS_3DLAYER:
state->common.texture.handle = 0;
if (state->layer3d.plugin >= 0)
ReignEngine_ReleasePlugin(state->layer3d.plugin);
if (state->layer3d.sprite_no >= 0)
sact_SP_Delete(state->layer3d.sprite_no);
break;
}
memset(state, 0, sizeof(struct parts_state));
}
@@ -216,10 +267,23 @@ void parts_state_reset(struct parts_state *state, enum parts_type type)
state->gauge.cg_no = -1;
state->gauge.rate = 0.0f;
break;
case PARTS_3DLAYER:
state->layer3d.plugin = -1;
state->layer3d.sprite_no = -1;
break;
case PARTS_UNINITIALIZED:
case PARTS_CG:
case PARTS_ANIMATION:
case PARTS_FLASH:
case PARTS_FLAT:
case PARTS_RECT_DETECTION:
break;
case PARTS_MOVIE:
state->movie.sprite_no = -1;
break;
case PARTS_LAYOUT_BOX:
state->layout_box.layout_type = PARTS_LAYOUT_VERTICAL;
state->layout_box.align = 1;
break;
}
}
@@ -288,6 +352,38 @@ struct parts_flash *parts_get_flash(struct parts *parts, int state)
return &parts->states[state].flash;
}
struct parts_flat *parts_get_flat(struct parts *parts, int state)
{
if (parts->states[state].type != PARTS_FLAT) {
parts_state_reset(&parts->states[state], PARTS_FLAT);
}
return &parts->states[state].flat;
}
struct parts_movie *parts_get_movie(struct parts *parts, int state)
{
if (parts->states[state].type != PARTS_MOVIE) {
parts_state_reset(&parts->states[state], PARTS_MOVIE);
}
return &parts->states[state].movie;
}
struct parts_layout_box *parts_get_layout_box(struct parts *parts)
{
if (parts->states[0].type != PARTS_LAYOUT_BOX) {
parts_state_reset(&parts->states[0], PARTS_LAYOUT_BOX);
}
return &parts->states[0].layout_box;
}
struct parts_3dlayer *parts_get_3dlayer(struct parts *parts, int state)
{
if (parts->states[state].type != PARTS_3DLAYER) {
parts_state_reset(&parts->states[state], PARTS_3DLAYER);
}
return &parts->states[state].layer3d;
}
static Point calculate_offset(int mode, int w, int h)
{
switch (mode) {
@@ -358,16 +454,16 @@ void parts_set_pos(struct parts *parts, Point pos)
parts->local.pos.x = pos.x;
parts->local.pos.y = pos.y;
parts_recalculate_hitbox(parts);
parts_update_global_pos(parts, parts->parent ? parts->parent->global.pos : root_pos);
parts_update_global_pos(parts, parts->parent ? parts->parent->global.pos : root_params.pos);
parts_dirty(parts);
}
void parts_set_global_pos(Point pos)
{
root_pos = pos;
root_params.pos = pos;
struct parts *parts;
PARTS_LIST_FOREACH(parts) {
parts_update_global_pos(parts, root_pos);
parts_update_global_pos(parts, root_params.pos);
}
parts_engine_dirty();
}
@@ -604,7 +700,15 @@ bool parts_cg_set(struct parts *parts, struct parts_cg *parts_cg, struct string
{
assert(cg_name && *(cg_name->text) != '\0');
int no;
struct cg *cg = asset_cg_load_by_name(cg_name->text, &no);
struct cg *cg;
if (!memcmp(cg_name->text, "<save>", 6)) {
char *path = savedir_path(cg_name->text + 6);
cg = cg_load_file(path);
no = 0;
free(path);
} else {
cg = asset_cg_load_by_name(cg_name->text, &no);
}
return _parts_cg_set(parts, parts_cg, cg, no, string_dup(cg_name));
}
@@ -648,7 +752,7 @@ void parts_numeral_font_init(struct parts_numeral_font *font)
x += font->width[i];
}
gfx_delete_texture(&t);
free(cg);
cg_free(cg);
}
}
@@ -714,7 +818,7 @@ static int parts_load_numeral_font_separate_string(struct string *cg_name)
return font_no;
}
static int parts_load_numeral_font_combined(struct cg *cg, int cg_no, int w[12])
static int parts_load_numeral_font_combined(int cg_no, int w[12])
{
// find existing font
for (int i = 0; i < parts_nr_numeral_fonts; i++) {
@@ -816,7 +920,11 @@ bool parts_numeral_set_number(struct parts *parts, struct parts_numeral *num, in
void parts_set_state(struct parts *parts, enum parts_state_type state)
{
if (parts->state != state && parts->states[state].type != PARTS_UNINITIALIZED) {
if (parts->lock_input_state)
return;
while (state > PARTS_STATE_DEFAULT && parts->states[state].type == PARTS_UNINITIALIZED)
state--;
if (parts->state != state) {
parts->state = state;
parts_dirty(parts);
}
@@ -830,20 +938,10 @@ void parts_set_surface_area(struct parts *parts, struct parts_common *common, in
parts_common_recalculate_hitbox(parts, common);
}
static void parts_update_loop(struct parts *parts, int passed_time)
static bool parts_animation_update(struct parts_animation *anim, int passed_time)
{
if (parts->states[parts->state].type == PARTS_FLASH) {
if (parts_flash_update(&parts->states[parts->state].flash, passed_time))
parts_dirty(parts);
return;
}
if (parts->states[parts->state].type != PARTS_ANIMATION)
return;
struct parts_animation *anim = &parts->states[parts->state].anim;
if (passed_time <= 0 || !anim->nr_frames)
return;
return false;
const unsigned elapsed = anim->elapsed + passed_time;
const unsigned frame_diff = elapsed / anim->frame_time;
@@ -853,12 +951,33 @@ static void parts_update_loop(struct parts *parts, int passed_time)
anim->elapsed = remainder;
anim->current_frame = (anim->current_frame + frame_diff) % anim->nr_frames;
anim->common.texture = anim->frames[anim->current_frame];
parts_dirty(parts);
return true;
} else {
anim->elapsed = elapsed;
return false;
}
}
static void parts_update_loop(struct parts *parts, int passed_time)
{
bool dirty = false;
switch (parts->states[parts->state].type) {
case PARTS_ANIMATION:
dirty = parts_animation_update(&parts->states[parts->state].anim, passed_time);
break;
case PARTS_FLASH:
dirty = parts_flash_update(&parts->states[parts->state].flash, passed_time);
break;
case PARTS_FLAT:
dirty = parts_flat_update(&parts->states[parts->state].flat, passed_time);
break;
default:
break;
}
if (dirty)
parts_dirty(parts);
}
static void parts_update_animation(int passed_time)
{
struct parts *parts;
@@ -874,6 +993,7 @@ void parts_release(int parts_no)
return;
struct parts *parts = slot->value;
parts_input_reset_drag(parts);
parts_clear_motion(parts);
for (int i = 0; i < PARTS_NR_STATES; i++) {
parts_state_free(&parts->states[i]);
@@ -884,6 +1004,7 @@ void parts_release(int parts_no)
struct parts *child = TAILQ_FIRST(&parts->children);
TAILQ_REMOVE(&parts->children, child, child_list_entry);
child->parent = NULL;
parts_component_dirty(child);
}
if (parts->parent) {
TAILQ_REMOVE(&parts->parent->children, parts, child_list_entry);
@@ -919,6 +1040,14 @@ void parts_release_all(void)
static bool parts_engine_initialized = false;
void PE_enable_multi_controller(void)
{
if (parts_multi_controller)
return;
assert(!parts_engine_initialized);
parts_multi_controller = true;
}
bool PE_Init(void)
{
if (parts_engine_initialized)
@@ -928,6 +1057,7 @@ bool PE_Init(void)
parts_table = ht_create(1024);
parts_render_init();
parts_debug_init();
ctrl_stack_init();
parts_engine_initialized = true;
return true;
}
@@ -935,6 +1065,8 @@ bool PE_Init(void)
void PE_Reset(void)
{
PE_ReleaseAllParts();
PE_ReleaseMessage();
ctrl_stack_init();
sact_ModuleFini();
}
@@ -965,14 +1097,16 @@ static void parts_combine_params(struct parts_params *parent, struct parts_param
out->multiply_color.r = parent->multiply_color.r * (child->multiply_color.r / 255.0f);
out->multiply_color.g = parent->multiply_color.g * (child->multiply_color.g / 255.0f);
out->multiply_color.b = parent->multiply_color.b * (child->multiply_color.b / 255.0f);
out->alpha_clipper_parts_no = child->alpha_clipper_parts_no
? child->alpha_clipper_parts_no : parent->alpha_clipper_parts_no;
}
static void parts_update_component(struct parts *parts)
{
if (parts->parent) {
parts_combine_params(&parts->parent->global, &parts->local, &parts->global);
}
if (parts->global.z != parts->sp.z) {
parts_combine_params(parts->parent ? &parts->parent->global : &root_params,
&parts->local, &parts->global);
if (parts_get_sprite_z(parts) != parts->sp.z
|| parts_get_sprite_z2(parts) != parts->sp.z2) {
parts_list_resort(parts);
}
@@ -981,6 +1115,8 @@ static void parts_update_component(struct parts *parts)
parts->dirty = false;
}
parts_do_layout(parts);
struct parts *child;
PARTS_FOREACH_CHILD(child, parts) {
parts_update_component(child);
@@ -989,6 +1125,11 @@ static void parts_update_component(struct parts *parts)
void PE_UpdateComponent(possibly_unused int passed_time)
{
// After loading a save, the game script may compute a negative time delta
// because it stores an absolute system.GetTime() value in the save data,
// which is meaningless across process restarts.
if (passed_time < 0)
passed_time = 0;
while (!TAILQ_EMPTY(&dirty_list)) {
// pop parts object from dirty list
struct parts *parts = TAILQ_FIRST(&dirty_list);
@@ -1003,6 +1144,10 @@ void PE_UpdateComponent(possibly_unused int passed_time)
}
parts->parent = parent;
TAILQ_INSERT_TAIL(&parent->children, parts, child_list_entry);
// if parent is layout box, mark it dirty so that it can re-layout its children
if (parent->states[0].type == PARTS_LAYOUT_BOX)
parts_component_dirty(parent);
}
// TODO: should the child be orphaned if it already has a parent and an invalid
// parent no is given?
@@ -1026,7 +1171,7 @@ void PE_Update(int passed_time, bool message_window_show)
audio_update();
parts_update_animation(passed_time);
PE_UpdateInputState(passed_time);
parts_render_update(passed_time);
parts_render_update();
}
void PE_UpdateParts(int passed_time, possibly_unused bool is_skip, bool message_window_show)
@@ -1034,7 +1179,7 @@ void PE_UpdateParts(int passed_time, possibly_unused bool is_skip, bool message_
parts_message_window_show = message_window_show;
audio_update();
parts_update_animation(passed_time);
parts_render_update(passed_time);
parts_render_update();
}
void PE_SetDelegateIndex(int parts_no, int delegate_index)
@@ -1048,12 +1193,13 @@ int PE_GetDelegateIndex(int parts_no)
return parts ? parts->delegate_index : -1;
}
bool PE_SetPartsCG(int parts_no, struct string *cg_name, possibly_unused int sprite_deform, int state)
bool PE_SetPartsCG(int parts_no, struct string *cg_name, int sprite_deform, int state)
{
if (!parts_state_valid(--state))
return false;
struct parts *parts = parts_get(parts_no);
parts->sprite_deform = sprite_deform;
if (!cg_name || *(cg_name->text) == '\0') {
parts_state_reset(&parts->states[state], PARTS_CG);
parts_dirty(parts);
@@ -1064,12 +1210,13 @@ bool PE_SetPartsCG(int parts_no, struct string *cg_name, possibly_unused int spr
return parts_cg_set(parts, cg, cg_name);
}
bool PE_SetPartsCG_by_index(int parts_no, int cg_no, possibly_unused int sprite_deform, int state)
bool PE_SetPartsCG_by_index(int parts_no, int cg_no, int sprite_deform, int state)
{
if (!parts_state_valid(--state))
return false;
struct parts *parts = parts_get(parts_no);
parts->sprite_deform = sprite_deform;
if (!cg_no) {
parts_state_reset(&parts->states[state], PARTS_CG);
parts_dirty(parts);
@@ -1082,12 +1229,13 @@ bool PE_SetPartsCG_by_index(int parts_no, int cg_no, possibly_unused int sprite_
// XXX: Rance Quest
bool PE_SetPartsCG_by_string_index(int parts_no, struct string *cg_name,
possibly_unused int sprite_deform, int state)
int sprite_deform, int state)
{
if (!parts_state_valid(--state))
return false;
struct parts *parts = parts_get(parts_no);
parts->sprite_deform = sprite_deform;
if (!cg_name) {
parts_state_reset(&parts->states[state], PARTS_CG);
parts_dirty(parts);
@@ -1134,6 +1282,18 @@ bool PE_SetPartsCGSurfaceArea(int parts_no, int x, int y, int w, int h, int stat
return true;
}
void PE_GetPartsCGSurfaceArea(int parts_no, int *x, int *y, int *w, int *h, int state)
{
if (!parts_state_valid(--state))
return;
struct parts_cg *cg = parts_get_cg(parts_get(parts_no), state);
*x = cg->common.surface_area.x;
*y = cg->common.surface_area.y;
*w = cg->common.surface_area.w;
*h = cg->common.surface_area.h;
}
int PE_GetPartsCGNumber(int parts_no, int state)
{
if (!parts_state_valid(--state)) {
@@ -1216,8 +1376,11 @@ bool parts_animation_set_cg(struct parts *parts, struct parts_animation *anim,
{
bool r = _parts_animation_set_cg(parts, anim, start_no, nr_frames, frame_time,
load_loop_cg_by_name, cg_name);
if (r)
if (r) {
if (anim->cg_name)
free_string(anim->cg_name);
anim->cg_name = string_dup(cg_name);
}
return r;
}
@@ -1269,6 +1432,7 @@ bool parts_gauge_set_cg(struct parts *parts, struct parts_gauge *g, struct strin
if (!cg)
return false;
_parts_set_gauge_cg(parts, g, cg);
cg_free(cg);
g->cg_no = cg_no;
return true;
}
@@ -1279,6 +1443,7 @@ bool parts_gauge_set_cg_by_index(struct parts *parts, struct parts_gauge *g, int
if (!cg)
return false;
_parts_set_gauge_cg(parts, g, cg);
cg_free(cg);
g->cg_no = cg_no;
return true;
}
@@ -1429,13 +1594,12 @@ bool PE_SetNumeralLinkedCGNumberWidthWidthList_by_index(int parts_no, int cg_no,
if (!parts_state_valid(--state))
return false;
struct cg *cg = asset_cg_load(cg_no);
if (!cg)
if (!asset_exists(ASSET_CG, cg_no))
return false;
int w[12] = { w0, w1, w2, w3, w4, w5, w6, w7, w8, w9, w_minus, w_comma };
struct parts_numeral *n = parts_get_numeral(parts_get(parts_no), state);
n->font_no = parts_load_numeral_font_combined(cg, cg_no, w);
n->font_no = parts_load_numeral_font_combined(cg_no, w);
return true;
}
@@ -1447,13 +1611,12 @@ bool PE_SetNumeralLinkedCGNumberWidthWidthList(int parts_no, struct string *cg_n
return false;
int no;
struct cg *cg = asset_cg_load_by_name(cg_name->text, &no);
if (!cg)
if (!asset_exists_by_name(ASSET_CG, cg_name->text, &no))
return false;
int w[12] = { w0, w1, w2, w3, w4, w5, w6, w7, w8, w9, w_minus, w_comma };
struct parts_numeral *n = parts_get_numeral(parts_get(parts_no), state);
n->font_no = parts_load_numeral_font_combined(cg, no, w);
n->font_no = parts_load_numeral_font_combined(no, w);
return true;
}
@@ -1540,6 +1703,25 @@ void PE_ReleaseAllPartsWithoutSystem(void)
parts_release_all();
}
void PE_ReleaseAllWithoutSystem(struct page **erase_number_list)
{
// Release all parts not belonging to the system overlay controller
struct parts *parts = TAILQ_FIRST(&parts_list);
while (parts) {
struct parts *next = TAILQ_NEXT(parts, parts_list_entry);
if (parts->controller_no != PARTS_CONTROLLER_SYSTEM_OVERLAY) {
*erase_number_list = array_pushback(*erase_number_list,
(union vm_value){.i = parts->no}, AIN_ARRAY_INT, -1);
parts_release(parts->no);
}
parts = next;
}
// Drop all normal controllers and add a fresh default one.
ctrl_stack.nr_controllers = 0;
PE_AddController(-1);
}
void PE_SetPos(int parts_no, int x, int y)
{
parts_set_pos(parts_get(parts_no), (Point){ x, y });
@@ -1562,8 +1744,6 @@ void PE_SetAlpha(int parts_no, int alpha)
void PE_SetPartsDrawFilter(int parts_no, int draw_filter)
{
if (draw_filter && draw_filter != 1)
UNIMPLEMENTED("(%d, %d)", parts_no, draw_filter);
parts_get(parts_no)->draw_filter = draw_filter;
}
@@ -1759,6 +1939,14 @@ float PE_GetPartsRotateZ(int parts_no)
return parts_get(parts_no)->local.rotation.z;
}
void PE_SetPartsAlphaClipperPartsNumber(int parts_no, int alpha_clipper_parts_no)
{
struct parts *parts = parts_get(parts_no);
parts->local.alpha_clipper_parts_no = alpha_clipper_parts_no;
parts_component_dirty(parts);
parts_dirty(parts);
}
void PE_SetPartsPixelDecide(int parts_no, bool pixel_decide)
{
//UNIMPLEMENTED("(%d, %s)", parts_no, pixel_decide ? "true" : "false");
@@ -1776,6 +1964,44 @@ bool PE_SetThumbnailMode(bool mode)
return true;
}
bool PE_save_thumbnail(struct string *filename, int reduction_factor)
{
if (reduction_factor < 1)
reduction_factor = 1;
Texture *src = gfx_main_surface();
int w = src->w / reduction_factor;
int h = src->h / reduction_factor;
// Downscale by repeatedly halving until we are within a factor of two of
// the target size, then do the final stretch. This avoids the aliasing
// that a single bilinear minification would otherwise produce.
Texture tmp, *cur = src;
bool have_tmp = false;
while (cur->w / 2 > w && cur->h / 2 > h) {
Texture next;
gfx_init_texture_blank(&next, cur->w / 2, cur->h / 2);
gfx_copy_stretch_with_alpha_map(&next, 0, 0, next.w, next.h, cur, 0, 0, cur->w, cur->h);
if (have_tmp)
gfx_delete_texture(&tmp);
tmp = next;
cur = &tmp;
have_tmp = true;
}
Texture dst;
gfx_init_texture_blank(&dst, w, h);
gfx_copy_stretch_with_alpha_map(&dst, 0, 0, w, h, cur, 0, 0, cur->w, cur->h);
if (have_tmp)
gfx_delete_texture(&tmp);
char *path = savedir_path(filename->text);
int r = gfx_save_texture(&dst, path, ALCG_QNT);
free(path);
gfx_delete_texture(&dst);
return !!r;
}
void PE_SetInputState(int parts_no, int state)
{
if (!parts_state_valid(--state)) {
@@ -1790,10 +2016,72 @@ int PE_GetInputState(int parts_no)
return parts_get(parts_no)->state + 1;
}
void PE_SetComponentType(int parts_no, int type, int state)
{
if (!parts_state_valid(--state))
return;
struct parts *parts = parts_get(parts_no);
enum parts_type pt = PARTS_UNINITIALIZED;
switch (type) {
case 8: pt = PARTS_LAYOUT_BOX; break;
case 11: pt = PARTS_CG; break;
case 12: pt = PARTS_ANIMATION; break;
case 13: pt = PARTS_TEXT; break;
case 14: pt = PARTS_HGAUGE; break;
case 15: pt = PARTS_VGAUGE; break;
case 16: pt = PARTS_NUMERAL; break;
case 17: pt = PARTS_RECT_DETECTION; break;
case 18: pt = PARTS_CONSTRUCTION_PROCESS; break;
case 20: pt = PARTS_FLAT; break;
case 21: pt = PARTS_3DLAYER; break;
case 22: pt = PARTS_MOVIE; break;
default:
VM_ERROR("unknown component type %d", type);
}
if (parts->states[state].type != pt)
parts_state_reset(&parts->states[state], pt);
}
int PE_GetComponentType(int parts_no, int state)
{
if (!parts_state_valid(--state))
return -1;
struct parts *parts = parts_try_get(parts_no);
if (!parts)
return -1;
switch (parts->states[state].type) {
case PARTS_LAYOUT_BOX: return 8;
case PARTS_UNINITIALIZED: // defaluts to CG
case PARTS_CG:
return 11;
case PARTS_ANIMATION: return 12;
case PARTS_TEXT: return 13;
case PARTS_HGAUGE: return 14;
case PARTS_VGAUGE: return 15;
case PARTS_NUMERAL: return 16;
case PARTS_RECT_DETECTION: return 17;
case PARTS_CONSTRUCTION_PROCESS: return 18;
case PARTS_FLAT: return 20;
case PARTS_3DLAYER: return 21;
case PARTS_MOVIE: return 22;
case PARTS_FLASH:
break;
}
VM_ERROR("unsupported component type %d", parts->states[state].type);
}
bool PE_SetPartsRectangleDetectionSize(int parts_no, int w, int h, int state)
{
UNIMPLEMENTED("(%d, %d, %d, %d)", parts_no, w, h, state);
return false;
if (!parts_state_valid(--state))
return false;
struct parts *parts = parts_try_get(parts_no);
if (!parts)
return false;
if (parts->states[state].type != PARTS_RECT_DETECTION)
parts_state_reset(&parts->states[state], PARTS_RECT_DETECTION);
parts_set_dims(parts, &parts->states[state].common, w, h);
return true;
}
bool PE_SetPartsCGDetectionSize(int parts_no, struct string *cg_name, int state)
@@ -1818,8 +2106,221 @@ bool PE_IsExist(int parts_no)
return !!ht_get_int(parts_table, parts_no, NULL);
}
void PE_SetSpeedupRateByMessageSkip(int parts_no, int rate)
static void ctrl_stack_init(void)
{
if (rate != 1)
UNIMPLEMENTED("(%d, %d)");
memset(&ctrl_stack, 0, sizeof(ctrl_stack));
// Add initial default controller
PE_AddController(-1);
}
// Adds a new controller to the stack and makes it active. The `index`
// parameter specifies the position in the stack at which to insert the new
// controller; -1 means "insert directly after the currently active
// controller". In practice the game only ever passes -1, and the active
// controller is always the top of the stack at that point, so this
// degenerates to a simple push.
int PE_AddController(int index)
{
if (index != -1)
VM_ERROR("index != -1 not supported (got %d)", index);
if (ctrl_stack.nr_controllers > 0 &&
ctrl_stack.active != ctrl_stack.nr_controllers - 1)
VM_ERROR("active controller is not at the top of the stack");
if (ctrl_stack.nr_controllers >= PARTS_CONTROLLER_STACK_MAX)
VM_ERROR("controller stack overflow");
int no = ctrl_stack.nr_controllers++;
ctrl_stack.active = no;
return no;
}
// Removes the controller at position `index` from the stack, releases all
// parts belonging to it, and returns their parts numbers in
// `erase_number_list`. `index == -1` means "remove the currently active
// controller". In practice the game only ever passes -1, and the active
// controller is always the top of the stack at that point, so this
// degenerates to a simple pop.
void PE_RemoveController(struct page **erase_number_list, int index)
{
if (index != -1)
VM_ERROR("index != -1 not supported (got %d)", index);
if (ctrl_stack.nr_controllers == 0 ||
ctrl_stack.active != ctrl_stack.nr_controllers - 1)
VM_ERROR("active controller is not at the top of the stack");
int ctrl_no = ctrl_stack.active;
// Collect and release parts belonging to this controller
struct parts *p = TAILQ_FIRST(&parts_list);
while (p) {
struct parts *next = TAILQ_NEXT(p, parts_list_entry);
if (p->controller_no == ctrl_no) {
*erase_number_list = array_pushback(*erase_number_list,
(union vm_value){.i = p->delegate_index}, AIN_ARRAY_INT, -1);
parts_release(p->no);
}
p = next;
}
ctrl_stack.nr_controllers--;
if (ctrl_stack.nr_controllers == 0) {
PE_AddController(-1);
} else {
ctrl_stack.active = ctrl_stack.nr_controllers - 1;
}
}
void PE_set_active_controller(int controller_no)
{
if (controller_no == PARTS_CONTROLLER_SYSTEM_OVERLAY ||
(controller_no >= 0 && controller_no < ctrl_stack.nr_controllers))
ctrl_stack.active = controller_no;
else
VM_ERROR("Invalid controller number: %d", controller_no);
}
int PE_get_active_controller(void)
{
return ctrl_stack.active;
}
int PE_get_system_controller(void)
{
return PARTS_CONTROLLER_SYSTEM_OVERLAY;
}
int PE_get_nr_controllers(void)
{
return ctrl_stack.nr_controllers;
}
void PE_parts_set_want_save(int parts_no, bool want_save)
{
parts_get(parts_no)->want_save = want_save;
}
float PE_parts_get_absolute_x(int parts_no)
{
struct parts *parts = parts_try_get(parts_no);
return parts ? (float)parts->global.pos.x : 0.0f;
}
float PE_parts_get_absolute_y(int parts_no)
{
struct parts *parts = parts_try_get(parts_no);
return parts ? (float)parts->global.pos.y : 0.0f;
}
int PE_parts_get_absolute_z(int parts_no)
{
struct parts *parts = parts_try_get(parts_no);
return parts ? parts->global.z : 0;
}
void PE_parts_set_lock_input_state(int parts_no, bool lock)
{
parts_get(parts_no)->lock_input_state = lock;
}
bool PE_init_parts_movie(int parts_no, int width, int height, int bg_r, int bg_g, int bg_b, int state)
{
if (!parts_state_valid(--state))
return false;
struct parts *parts = parts_get(parts_no);
struct parts_movie *movie = parts_get_movie(parts, state);
int sp_no = sact_SP_GetUnuseNum(0);
struct sact_sprite *sp = sact_create_sprite(sp_no, width, height, bg_r, bg_g, bg_b, 255);
if (!sp)
return false;
// The movie frames are composited by the parts engine via parts_render(),
// so hide the bound sprite from the scene to avoid double-drawing.
sprite_set_show(sp, false);
struct texture *tex = sprite_get_texture(sp);
movie->sprite_no = sp_no;
movie->common.texture = *tex; // XXX: textures normally shouldn't be copied like this...
parts_set_dims(parts, &movie->common, width, height);
parts_dirty(parts);
return true;
}
int PE_get_movie_sprite(int parts_no, int state)
{
if (!parts_state_valid(--state))
return -1;
struct parts *parts = parts_try_get(parts_no);
if (!parts || parts->states[state].type != PARTS_MOVIE)
return -1;
return parts->states[state].movie.sprite_no;
}
bool parts_3dlayer_create_plugin(struct parts_3dlayer *l)
{
if (l->plugin >= 0)
return false;
int handle = ReignEngine_create_plugin(RE_SEAL_PLUGIN);
if (handle < 0)
return false;
int sp_no = sact_SP_GetUnuseNum(0);
struct sact_sprite *sp = sact_create_sprite(sp_no, 1, 1, 0, 0, 0, 255);
if (!sp) {
ReignEngine_ReleasePlugin(handle);
return false;
}
if (!ReignEngine_BindPlugin(handle, sp_no)) {
sact_SP_Delete(sp_no);
ReignEngine_ReleasePlugin(handle);
return false;
}
// The 3D content is composited by the parts engine via parts_render(), so
// hide the bound sprite from the scene to avoid double-drawing.
sprite_set_show(sp, false);
l->plugin = handle;
l->sprite_no = sp_no;
return true;
}
bool PE_CreateParts3DLayerPluginID(int parts_no, int state)
{
if (!parts_state_valid(--state))
return false;
struct parts *parts = parts_get(parts_no);
return parts_3dlayer_create_plugin(parts_get_3dlayer(parts, state));
}
int PE_GetParts3DLayerPluginID(int parts_no, int state)
{
if (!parts_state_valid(--state))
return -1;
struct parts *parts = parts_try_get(parts_no);
if (!parts || parts->states[state].type != PARTS_3DLAYER)
return -1;
return parts->states[state].layer3d.plugin;
}
bool PE_ReleaseParts3DLayerPluginID(int parts_no, int state)
{
if (!parts_state_valid(--state))
return false;
struct parts *parts = parts_try_get(parts_no);
if (!parts || parts->states[state].type != PARTS_3DLAYER)
return false;
struct parts_3dlayer *l = &parts->states[state].layer3d;
if (l->plugin < 0)
return false;
ReignEngine_ReleasePlugin(l->plugin);
l->plugin = -1;
if (l->sprite_no >= 0) {
sact_SP_Delete(l->sprite_no);
l->sprite_no = -1;
}
return true;
}
+218 -18
View File
@@ -82,7 +82,7 @@ struct sound_motion {
struct parts_text_char {
Texture t;
char ch[4];
float advance;
int advance;
Point off;
};
@@ -103,7 +103,12 @@ enum parts_type {
PARTS_VGAUGE,
PARTS_CONSTRUCTION_PROCESS,
PARTS_FLASH,
#define PARTS_NR_TYPES (PARTS_FLASH+1)
PARTS_FLAT,
PARTS_MOVIE,
PARTS_RECT_DETECTION,
PARTS_LAYOUT_BOX,
PARTS_3DLAYER,
#define PARTS_NR_TYPES (PARTS_3DLAYER+1)
};
struct parts_common {
@@ -124,7 +129,7 @@ struct parts_text {
struct parts_common common;
unsigned nr_lines;
struct parts_text_line *lines;
unsigned line_space;
int line_space;
struct { float x; int y; } cursor;
struct text_style ts;
};
@@ -173,6 +178,7 @@ struct parts_gauge {
float rate;
};
// Serialized in save data. Do not reorder.
enum parts_cp_op_type {
PARTS_CP_CREATE,
PARTS_CP_CREATE_PIXEL_ONLY,
@@ -185,7 +191,9 @@ enum parts_cp_op_type {
PARTS_CP_COPY_CUT_CG,
PARTS_CP_DRAW_TEXT,
PARTS_CP_COPY_TEXT,
#define PARTS_NR_CP_TYPES (PARTS_CP_COPY_TEXT+1)
PARTS_CP_GRAY_FILTER,
PARTS_CP_FILL_WITH_ALPHA,
#define PARTS_NR_CP_TYPES (PARTS_CP_FILL_WITH_ALPHA+1)
};
struct parts_cp_create {
@@ -194,7 +202,7 @@ struct parts_cp_create {
};
struct parts_cp_cg {
int no;
struct string *name;
};
struct parts_cp_fill {
@@ -203,7 +211,7 @@ struct parts_cp_fill {
};
struct parts_cp_cut_cg {
int cg_no;
struct string *cg_name;
int dx, dy, dw, dh;
int sx, sy, sw, sh;
int interp_type;
@@ -216,6 +224,11 @@ struct parts_cp_text {
struct text_style style;
};
struct parts_cp_filter {
int x, y, w, h;
bool full_size;
};
struct parts_cp_op {
TAILQ_ENTRY(parts_cp_op) entry;
enum parts_cp_op_type type;
@@ -225,6 +238,7 @@ struct parts_cp_op {
struct parts_cp_fill fill;
struct parts_cp_cut_cg cut_cg;
struct parts_cp_text text;
struct parts_cp_filter filter;
};
};
@@ -251,6 +265,13 @@ enum parts_flash_blend_mode {
PARTS_FLASH_BLEND_HARDLIGHT = 14,
};
enum parts_draw_filter {
PARTS_DRAW_FILTER_NORMAL = 0,
PARTS_DRAW_FILTER_ADDITIVE = 1,
PARTS_DRAW_FILTER_MULTIPLY = 2,
PARTS_DRAW_FILTER_SCREEN = 3,
};
struct parts_flash_object {
TAILQ_ENTRY(parts_flash_object) entry;
uint16_t depth;
@@ -266,6 +287,7 @@ struct parts_flash {
struct swf *swf;
struct swf_tag *tag;
bool has_ended;
bool stopped;
unsigned elapsed;
int current_frame;
@@ -275,6 +297,76 @@ struct parts_flash {
TAILQ_HEAD(, parts_flash_object) display_list;
};
struct flat_layer_state {
int current_frame;
bool stopped;
bool suppress_advance;
int jump_target; // pending jump frame, -1 = none
// Per-timeline: last matched script key index for change detection
// (-2 = uninitialized, -1 = no match, >= 0 = key index)
int *last_script_keys;
// Per-timeline: child layer state (for TIMELINE libs), NULL otherwise
struct flat_layer_state **children;
size_t nr_timelines;
};
// Per-frame CG list for a STOP_MOTION library. lib_indices[k] is
// the library index of the CG to display at frame k.
struct flat_stop_motion_frames {
int *lib_indices;
int count;
};
struct parts_flat {
struct parts_common common;
struct string *name;
struct flat *flat;
bool stopped;
bool needs_advance;
unsigned elapsed;
int end_frame;
int pending_seek_delta;
struct flat_layer_state *root_state;
size_t nr_libraries;
Texture *textures; // indexed by library index (only CG libs have valid textures)
// Indexed by library index. Only entries for STOP_MOTION libraries have lib_indices populated.
struct flat_stop_motion_frames *stop_motion_frames;
};
struct parts_movie {
struct parts_common common;
int sprite_no; // SACT sprite number used as movie render target
};
enum parts_layout_type {
PARTS_LAYOUT_FREE = 0, // no automatic layout
PARTS_LAYOUT_VERTICAL = 1,
PARTS_LAYOUT_HORIZONTAL = 2,
};
// In AliceSoft's PartsEngine implementation, LayoutBox is a component type
// that is NOT per-state. We store it per-state for uniformity with other
// component types, but parts_get_layout_box() and all code in layoutbox.c
// operate on states[0] only. This is safe as long as game scripts never
// convert a LayoutBox parts to/from another component type.
struct parts_layout_box {
struct parts_common common;
enum parts_layout_type layout_type;
bool wrap;
int wrap_size;
int align;
int padding_top;
int padding_bottom;
int padding_left;
int padding_right;
};
struct parts_3dlayer {
struct parts_common common;
int plugin; // ReignEngine plugin handle (-1 = none)
int sprite_no; // SACT sprite used as render target
};
struct parts_state {
enum parts_type type;
union {
@@ -286,6 +378,10 @@ struct parts_state {
struct parts_gauge gauge;
struct parts_construction_process cproc;
struct parts_flash flash;
struct parts_flat flat;
struct parts_movie movie;
struct parts_layout_box layout_box;
struct parts_3dlayer layer3d;
};
};
@@ -300,6 +396,7 @@ struct parts_params {
struct { float x, y, z; } rotation;
SDL_Color add_color;
SDL_Color multiply_color;
int alpha_clipper_parts_no;
};
struct parts {
@@ -318,22 +415,47 @@ struct parts {
int delegate_index;
int sprite_deform;
bool clickable;
bool pass_cursor;
bool lock_input_state;
bool want_save;
bool draggable;
int on_cursor_sound;
int on_click_sound;
int origin_mode;
int pending_parent;
int linked_to;
int linked_from;
bool is_hovered;
int hover_time;
int draw_filter;
bool message_window;
int margin_top;
int margin_bottom;
int margin_left;
int margin_right;
struct parts_motion_list motion;
int controller_no;
};
#define PARTS_LIST_FOREACH(iter) TAILQ_FOREACH(iter, &parts_list, parts_list_entry)
#define PARTS_LIST_FOREACH_REVERSE(iter) TAILQ_FOREACH_REVERSE(iter, &parts_list, parts_list, parts_list_entry)
#define PARTS_FOREACH_CHILD(iter, parent) TAILQ_FOREACH(iter, &parent->children, child_list_entry)
// parts.c
extern struct parts_list parts_list;
// Controllers are identified by their position in the stack (0 = bottom). The
// system overlay controller lives outside the stack.
#define PARTS_CONTROLLER_STACK_MAX 10000
#define PARTS_CONTROLLER_SYSTEM_OVERLAY PARTS_CONTROLLER_STACK_MAX
struct parts_controller_stack {
int nr_controllers;
int active; // stack index or PARTS_CONTROLLER_SYSTEM_OVERLAY
};
extern struct parts_controller_stack ctrl_stack;
extern bool parts_multi_controller;
struct parts *parts_try_get(int parts_no);
struct parts *parts_get(int parts_no);
struct parts_cg *parts_get_cg(struct parts *parts, int state);
@@ -344,6 +466,10 @@ struct parts_gauge *parts_get_hgauge(struct parts *parts, int state);
struct parts_gauge *parts_get_vgauge(struct parts *parts, int state);
struct parts_construction_process *parts_get_construction_process(struct parts *parts, int state);
struct parts_flash *parts_get_flash(struct parts *parts, int state);
struct parts_flat *parts_get_flat(struct parts *parts, int state);
struct parts_movie *parts_get_movie(struct parts *parts, int state);
struct parts_layout_box *parts_get_layout_box(struct parts *parts);
struct parts_3dlayer *parts_get_3dlayer(struct parts *parts, int state);
void parts_set_pos(struct parts *parts, Point pos);
void parts_set_global_pos(Point pos);
void parts_set_dims(struct parts *parts, struct parts_common *common, int w, int h);
@@ -378,6 +504,7 @@ bool parts_gauge_set_cg(struct parts *parts, struct parts_gauge *g, struct strin
bool parts_gauge_set_cg_by_index(struct parts *parts, struct parts_gauge *g, int cg_no);
void parts_hgauge_set_rate(struct parts *parts, struct parts_gauge *g, float rate);
void parts_vgauge_set_rate(struct parts *parts, struct parts_gauge *g, float rate);
bool parts_3dlayer_create_plugin(struct parts_3dlayer *l);
// text.c
void parts_text_free(struct parts_text *t);
@@ -386,7 +513,7 @@ struct string *parts_text_get(struct parts_text *t);
// render.c
void parts_render_init(void);
void parts_render_update(int passed_time);
void parts_render_update(void);
void parts_engine_dirty(void);
void parts_engine_clean(void);
void parts_dirty(struct parts *parts);
@@ -399,8 +526,30 @@ void parts_clear_motion(struct parts *parts);
void parts_add_motion(struct parts *parts, struct parts_motion *motion);
// input.c
extern Point parts_prev_pos;
extern bool parts_began_click;
void parts_input_reset_drag(struct parts *parts);
// message.c
enum parts_message_type {
PARTS_MSG_MOUSE_ENTER = 1,
PARTS_MSG_MOUSE_MOVE = 2,
PARTS_MSG_MOUSE_LEAVE = 3,
PARTS_MSG_MOUSE_WHEEL = 4,
PARTS_MSG_MOUSE_CLICK = 5,
PARTS_MSG_MOUSE_ON = 6,
PARTS_MSG_DRAG_BEGIN = 7,
PARTS_MSG_DRAGGING = 8,
PARTS_MSG_DRAG_END = 9,
PARTS_MSG_DROP_ENTER = 10,
PARTS_MSG_DROP_ON = 11,
PARTS_MSG_DROPPED = 12,
PARTS_MSG_DROP_LEAVE = 13,
PARTS_MSG_KEY_TRIGGER = 14,
PARTS_MSG_KEY_DOWN = 15,
PARTS_MSG_KEY_UP = 17,
};
void parts_msg_push(struct parts* parts, int type, const char *fmt, ...);
// construction.c
void parts_cp_op_free(struct parts_cp_op *op);
@@ -415,6 +564,67 @@ bool parts_flash_load(struct parts *parts, struct parts_flash *f, struct string
bool parts_flash_update(struct parts_flash *f, int passed_time);
bool parts_flash_seek(struct parts_flash *f, int frame);
// flat.c
void parts_flat_free(struct parts_flat *f);
bool parts_flat_load(struct parts *parts, struct parts_flat *f, struct string *filename);
bool parts_flat_update(struct parts_flat *f, int passed_time);
int parts_flat_find_library(struct flat *fl, const char *name);
int parts_flat_stop_motion_get_cg_lib(struct parts_flat *f, int sm_lib_idx, int local);
struct flat_emitter;
struct flat_key_data_graphic;
struct flat_emitter_particle {
vec2 pos; // emitter-space position (pixels)
vec2 scale;
vec3 rot; // degrees (x, y, z)
float fade_alpha; // 0-1.0
int cg_lib_idx; // CG library index of the texture for this particle
};
// Per-key emitter properties after applying the emitter's inherit_* flags.
struct flat_emitter_layer_effective {
vec2 pos;
bool reverse_lr, reverse_tb;
float alpha;
vec3 add_color;
vec3 mul_color;
int draw_filter;
bool use_scale;
bool use_rotation;
bool use_origin;
};
#define FLAT_MAX_ANCESTOR_DEPTH 32
struct flat_key_stack {
const struct flat_key_data_graphic *keys[FLAT_MAX_ANCESTOR_DEPTH];
int count;
};
typedef void (*flat_emitter_particle_fn)(const struct flat_emitter_particle *p,
void *ud);
void parts_flat_align_offset(int align, int w, int h, vec2 out);
bool parts_flat_emitter_get_align_offset(struct parts_flat *f, int emitter_lib_idx, vec2 out);
void parts_flat_foreach_emitter_particle(struct parts_flat *f, int emitter_lib_idx,
const struct flat_key_data_graphic *keys,
int birth_frame, int age, int frame_count,
flat_emitter_particle_fn fn, void *ud);
void parts_flat_build_layer_matrix(const struct flat_key_data_graphic *key,
vec2 pos,
bool use_rotation, bool use_scale, bool use_origin,
bool reverse_lr, bool reverse_tb,
mat4 out);
void parts_flat_build_emitter_base_matrix(const struct flat_emitter *em,
const struct flat_key_stack *stack, mat4 out);
void parts_flat_emitter_resolve_layer(
const struct flat_emitter *em,
const struct flat_key_data_graphic *key,
float parts_alpha, float layer_alpha,
struct flat_emitter_layer_effective *out);
// layoutbox.c
void parts_do_layout(struct parts *parts);
// debug.c
struct sprite;
void parts_debug_init(void);
@@ -426,14 +636,4 @@ static inline bool parts_state_valid(int state)
return state >= 0 && state <= 2;
}
static inline int parts_get_width(struct parts *parts)
{
return parts->states[parts->state].common.w;
}
static inline int parts_get_height(struct parts *parts)
{
return parts->states[parts->state].common.h;
}
#endif /* SYSTEM4_PARTS_INTERNAL_H */
+415 -51
View File
@@ -15,27 +15,23 @@
*/
#include <limits.h>
#include <math.h>
#include <assert.h>
#include <cglm/cglm.h>
#include "system4.h"
#include "system4/hashtable.h"
#include "system4/string.h"
#include "system4/flat.h"
#include "gfx/gfx.h"
#include "sact.h"
#include "scene.h"
#include "sprite.h"
#include "xsystem4.h"
#include "parts_internal.h"
#ifndef M_PI
#define M_PI (3.14159265358979323846)
#endif
static inline float deg2rad(float deg)
{
return deg * (M_PI / 180.0);
}
static struct {
struct shader shader;
GLint blend_rate;
@@ -43,31 +39,30 @@ static struct {
GLint top_right;
GLint add_color;
GLint multiply_color;
GLint draw_filter;
GLint use_clipper;
GLint clipper_tex;
GLint inv_clipper_transform;
} parts_shader;
static void parts_render_text(struct parts *parts, struct parts_text *t)
static void set_draw_filter_blend_func(int draw_filter)
{
int x = parts->global.pos.x + t->common.origin_offset.x;
int y = parts->global.pos.y + t->common.origin_offset.y;
for (int i = 0; i < t->nr_lines; i++) {
struct parts_text_line *line = &t->lines[i];
for (int j = 0; j < line->nr_chars; j++) {
struct parts_text_char *ch = &line->chars[j];
Rectangle rect = {
.x = x,
.y = y,
.w = ch->t.w,
.h = ch->t.h
};
gfx_render_texture(&ch->t, &rect);
x += ch->advance;
}
x = parts->global.pos.x + t->common.origin_offset.x;
y += line->height;
switch (draw_filter) {
case PARTS_DRAW_FILTER_ADDITIVE:
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE, GL_ZERO, GL_ONE);
break;
case PARTS_DRAW_FILTER_MULTIPLY:
glBlendFuncSeparate(GL_DST_COLOR, GL_ZERO, GL_ZERO, GL_ONE);
break;
case PARTS_DRAW_FILTER_SCREEN:
glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_COLOR, GL_ZERO, GL_ONE);
break;
default:
break;
}
}
static void parts_render_texture(struct texture *texture, mat4 mw_transform, Rectangle *rect, float blend_rate, vec3 add_color, vec3 multiply_color)
static void parts_render_texture(struct texture *texture, mat4 mw_transform, Rectangle *rect, float blend_rate, vec3 add_color, vec3 multiply_color, int draw_filter, int alpha_clipper)
{
mat4 wv_transform = WV_TRANSFORM(config.view_width, config.view_height);
@@ -86,28 +81,98 @@ static void parts_render_texture(struct texture *texture, mat4 mw_transform, Rec
glUniform2f(parts_shader.top_right, rect->x + rect->w, rect->y + rect->h);
glUniform3fv(parts_shader.add_color, 1, add_color);
glUniform3fv(parts_shader.multiply_color, 1, multiply_color);
glUniform1i(parts_shader.draw_filter, draw_filter);
struct parts *clipper = alpha_clipper ? parts_try_get(alpha_clipper) : NULL;
if (clipper) {
struct parts_common *c_common = &clipper->states[clipper->state].common;
// Calculate the inverse of the clipper's world matrix.
mat4 clip_mw = GLM_MAT4_IDENTITY_INIT;
glm_translate(clip_mw, (vec3) { clipper->global.pos.x, clipper->global.pos.y, 0 });
glm_rotate_z(clip_mw, glm_rad(clipper->local.rotation.z), clip_mw);
glm_scale(clip_mw, (vec3){ clipper->global.scale.x, clipper->global.scale.y, 1.0 });
glm_translate(clip_mw, (vec3){ c_common->origin_offset.x, c_common->origin_offset.y, 0 });
glm_scale(clip_mw, (vec3){ c_common->w, c_common->h, 1.0 });
mat4 clip_inv;
glm_mat4_inv(clip_mw, clip_inv);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, c_common->texture.handle);
glActiveTexture(GL_TEXTURE0);
glUniform1i(parts_shader.use_clipper, 1);
glUniform1i(parts_shader.clipper_tex, 1);
glUniformMatrix4fv(parts_shader.inv_clipper_transform, 1, GL_FALSE, clip_inv[0]);
} else {
glUniform1i(parts_shader.use_clipper, 0);
}
gfx_run_job(&job);
}
static void parts_render_text(struct parts *parts, struct parts_text *t)
{
vec3 add_color = {
parts->global.add_color.r / 255.0f,
parts->global.add_color.g / 255.0f,
parts->global.add_color.b / 255.0f
};
vec3 multiply_color = {
parts->global.multiply_color.r / 255.0f,
parts->global.multiply_color.g / 255.0f,
parts->global.multiply_color.b / 255.0f,
};
float blend_rate = parts->global.alpha / 255.0;
int x = parts->global.pos.x + t->common.origin_offset.x;
int y = parts->global.pos.y + t->common.origin_offset.y;
for (int i = 0; i < t->nr_lines; i++) {
struct parts_text_line *line = &t->lines[i];
for (int j = 0; j < line->nr_chars; j++) {
struct parts_text_char *ch = &line->chars[j];
mat4 mw_transform = WORLD_TRANSFORM(ch->t.w, ch->t.h, x, y);
Rectangle r = { 0, 0, ch->t.w, ch->t.h };
parts_render_texture(&ch->t, mw_transform, &r, blend_rate, add_color,
multiply_color, 0, parts->global.alpha_clipper_parts_no);
x += ch->advance;
}
x = parts->global.pos.x + t->common.origin_offset.x;
y += line->height + t->line_space;
}
}
static void parts_render_cg(struct parts *parts, struct parts_common *common)
{
switch (parts->draw_filter) {
case 1:
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE, GL_ZERO, GL_ONE);
break;
default:
break;
}
set_draw_filter_blend_func(parts->draw_filter);
mat4 mw_transform = GLM_MAT4_IDENTITY_INIT;
glm_translate(mw_transform, (vec3) { parts->global.pos.x, parts->global.pos.y, 0 });
// FIXME: need perspective for 3D rotate
//glm_rotate_x(mw_transform, parts->rotation.x, mw_transform);
//glm_rotate_y(mw_transform, parts->rotation.y, mw_transform);
glm_rotate_z(mw_transform, parts->local.rotation.z * (M_PI/180.0), mw_transform);
glm_rotate_z(mw_transform, glm_rad(parts->local.rotation.z), mw_transform);
glm_scale(mw_transform, (vec3){ parts->global.scale.x, parts->global.scale.y, 1.0 });
glm_translate(mw_transform, (vec3){ common->origin_offset.x, common->origin_offset.y, 0 });
glm_scale(mw_transform, (vec3){ common->w, common->h, 1.0 });
switch (parts->sprite_deform) {
// Flip horizontally
case 1:
glm_translate(mw_transform, (vec3){ common->w, 0.0f, 0.0f });
glm_scale(mw_transform, (vec3){ -common->w, common->h, 1.0f });
break;
// Flip vertically
case 2:
glm_translate(mw_transform, (vec3){ 0.0f, common->h, 0.0f });
glm_scale(mw_transform, (vec3){ common->w, -common->h, 1.0f });
break;
default:
WARNING("Invalid sprite_deform: %d", parts->sprite_deform);
// No deform
case 0:
glm_scale(mw_transform, (vec3){ common->w, common->h, 1.0 });
break;
}
Rectangle r = common->surface_area;
if (!r.w && !r.h) {
@@ -124,11 +189,303 @@ static void parts_render_cg(struct parts *parts, struct parts_common *common)
parts->global.multiply_color.g / 255.0f,
parts->global.multiply_color.b / 255.0f,
};
parts_render_texture(&common->texture, mw_transform, &r, parts->global.alpha / 255.0, add_color, multiply_color);
parts_render_texture(&common->texture, mw_transform, &r, parts->global.alpha / 255.0,
add_color, multiply_color, parts->draw_filter,
parts->global.alpha_clipper_parts_no);
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
}
struct emitter_render_ud {
struct parts_flat *f;
mat4 transform; // root * base * layer for this emitter+birth_frame
float parent_alpha;
vec2 align;
int alpha_clipper;
int draw_filter;
vec3 add_color;
vec3 mul_color;
};
static void render_emitter_particle_cb(const struct flat_emitter_particle *p,
void *ud)
{
struct emitter_render_ud *d = ud;
if (p->cg_lib_idx < 0 || (size_t)p->cg_lib_idx >= d->f->nr_libraries)
return;
Texture *tex = &d->f->textures[p->cg_lib_idx];
if (!tex->handle)
return;
mat4 m = GLM_MAT4_IDENTITY_INIT;
glm_translate(m, (vec3){ p->pos[0], p->pos[1], 0 });
if (p->rot[2] != 0)
glm_rotate_z(m, glm_rad(p->rot[2]), m);
if (p->rot[0] != 0)
glm_rotate_x(m, glm_rad(-p->rot[0]), m);
if (p->rot[1] != 0)
glm_rotate_y(m, glm_rad(p->rot[1]), m);
glm_scale(m, (vec3){ p->scale[0], p->scale[1], 1.0f });
glm_translate(m, (vec3){ d->align[0], d->align[1], 0 });
// Bring particle into screen space, then kill the Z-output row so
// clip_z stays at the near plane.
glm_mat4_mul(d->transform, m, m);
m[0][2] = m[1][2] = m[2][2] = m[3][2] = 0.0f;
glm_scale(m, (vec3){ tex->w, tex->h, 1.0f });
if (d->draw_filter != PARTS_DRAW_FILTER_NORMAL)
set_draw_filter_blend_func(d->draw_filter);
float blend_rate = d->parent_alpha * p->fade_alpha;
Rectangle rect = { 0, 0, tex->w, tex->h };
parts_render_texture(tex, m, &rect, blend_rate,
d->add_color, d->mul_color, d->draw_filter, d->alpha_clipper);
if (d->draw_filter != PARTS_DRAW_FILTER_NORMAL)
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
}
static void render_flat_emitter(struct parts *parts, struct parts_flat *f,
int emitter_lib_idx, int local, int frame_count,
struct flat_key_data_graphic *keys,
mat4 root, float parent_alpha,
struct flat_key_stack *key_stack)
{
vec2 align;
if (!parts_flat_emitter_get_align_offset(f, emitter_lib_idx, align))
return;
struct flat_emitter *em = &f->flat->libraries[emitter_lib_idx].emitter;
int active_frames = max(1, frame_count - em->particle_lifetime + 1);
float parts_alpha = parts->global.alpha / 255.0f;
// TODO: key_stack reflects the current render frame, but each particle
// should see the ancestor keyframes at its birth frame instead.
// Currently all particles of this emitter share the same ancestor pose,
// which is noticeable only on `猿玉/取得済みマーク.flat` in Rance 9.
// Fixing this requires per-emitter state in parts_flat_update to record
// ancestor keys at each birth frame; base would then depend on birth_frame.
mat4 base;
parts_flat_build_emitter_base_matrix(em, key_stack, base);
int min_birth = max(0, local - em->particle_lifetime + 1);
int max_birth = min(active_frames - 1, local);
for (int birth_frame = min_birth; birth_frame <= max_birth; birth_frame++) {
int age = local - birth_frame;
// Use the birth frame's key transform so particles stay at their
// birth position while the emitter moves.
struct flat_key_data_graphic *birth_key = &keys[birth_frame];
float layer_alpha = parent_alpha * birth_key->alpha / 255.0f;
struct flat_emitter_layer_effective eff;
parts_flat_emitter_resolve_layer(em, birth_key,
parts_alpha, layer_alpha, &eff);
// Build per-birth-frame layer matrix from birth_key and the emitter's
// inherit_* flags.
mat4 layer_m;
parts_flat_build_layer_matrix(birth_key, eff.pos,
eff.use_rotation, eff.use_scale, eff.use_origin,
eff.reverse_lr, eff.reverse_tb,
layer_m);
struct emitter_render_ud ud = {
.f = f,
.parent_alpha = eff.alpha,
.alpha_clipper = parts->global.alpha_clipper_parts_no,
.draw_filter = eff.draw_filter,
};
glm_vec2_copy(align, ud.align);
glm_vec3_copy(eff.add_color, ud.add_color);
glm_vec3_copy(eff.mul_color, ud.mul_color);
glm_mat4_mul(base, layer_m, ud.transform);
glm_mat4_mul(root, ud.transform, ud.transform);
parts_flat_foreach_emitter_particle(f, emitter_lib_idx, keys,
birth_frame, age, frame_count,
render_emitter_particle_cb, &ud);
}
}
struct flat_draw_ctx {
mat4 matrix;
float alpha;
vec3 add_color;
vec3 mul_color;
int draw_filter;
};
static void render_flat_layer(struct parts *parts, struct parts_flat *f,
struct flat_layer_state *state,
struct flat_timeline *timelines, size_t nr_timelines,
struct flat_draw_ctx *ctx, mat4 root,
struct flat_key_stack *key_stack);
static void render_flat_cg(struct parts *parts, Texture *tex,
struct flat_key_data_graphic *key, struct flat_draw_ctx *ctx)
{
if (!tex->handle)
return;
set_draw_filter_blend_func(ctx->draw_filter);
Rectangle rect;
if (key->area_width && key->area_height) {
rect = (Rectangle){ key->area_x, key->area_y, key->area_width, key->area_height };
} else {
rect = (Rectangle){ 0, 0, tex->w, tex->h };
}
vec2 align;
parts_flat_align_offset(key->align, rect.w, rect.h, align);
mat4 render_m;
glm_mat4_copy(ctx->matrix, render_m);
// Kill the Z-output row to pin clip_z at the near plane, avoiding
// near/far clipping of 3D-rotated sprites.
render_m[0][2] = render_m[1][2] = render_m[2][2] = render_m[3][2] = 0.0f;
glm_translate(render_m, (vec3){ align[0], align[1], 0.0f });
// area_x/area_y select a sub-rectangle of the texture atlas, but
// should not shift the on-screen position. This translation cancels
// the offset that the sub-rect's top-left would otherwise introduce.
glm_translate(render_m, (vec3){ -(float)key->area_x, -(float)key->area_y, 0.0f });
glm_scale(render_m, (vec3){ tex->w, tex->h, 1.0f });
parts_render_texture(tex, render_m, &rect, ctx->alpha, ctx->add_color, ctx->mul_color,
ctx->draw_filter, parts->global.alpha_clipper_parts_no);
if (ctx->draw_filter != PARTS_DRAW_FILTER_NORMAL)
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
}
static void render_flat_item(struct parts *parts, struct parts_flat *f,
struct flat_layer_state *state, size_t tl_idx,
struct flat_timeline *tl, int local,
struct flat_draw_ctx *parent, mat4 root,
struct flat_key_stack *key_stack)
{
int lib_idx = parts_flat_find_library(f->flat, tl->library_name->text);
if (lib_idx < 0 || (size_t)lib_idx >= f->flat->nr_libraries)
return;
struct flat_library *lib = &f->flat->libraries[lib_idx];
if (lib->type == FLAT_LIB_EMITTER) {
render_flat_emitter(parts, f, lib_idx, local, tl->frame_count,
tl->graphic.keys,
root, parent->alpha, key_stack);
return;
}
struct flat_key_data_graphic *key = &tl->graphic.keys[local];
mat4 layer_m;
vec2 pos = { key->pos_x, key->pos_y };
parts_flat_build_layer_matrix(key, pos,
true, true, true,
key->reverse_lr, key->reverse_tb,
layer_m);
struct flat_draw_ctx ctx;
glm_mat4_mul(parent->matrix, layer_m, ctx.matrix);
ctx.alpha = parent->alpha * key->alpha / 255.0f;
vec3 key_add = { key->add_r / 255.0f, key->add_g / 255.0f, key->add_b / 255.0f };
vec3 key_mul = { key->mul_r / 255.0f, key->mul_g / 255.0f, key->mul_b / 255.0f };
glm_vec3_add(parent->add_color, key_add, ctx.add_color);
glm_vec3_mul(parent->mul_color, key_mul, ctx.mul_color);
ctx.draw_filter = key->draw_filter != PARTS_DRAW_FILTER_NORMAL
? key->draw_filter : parent->draw_filter;
switch (lib->type) {
case FLAT_LIB_CG:
render_flat_cg(parts, &f->textures[lib_idx], key, &ctx);
break;
case FLAT_LIB_TIMELINE: {
struct flat_layer_state *child = state->children[tl_idx];
if (child && key_stack->count < FLAT_MAX_ANCESTOR_DEPTH) {
key_stack->keys[key_stack->count++] = key;
render_flat_layer(parts, f, child,
lib->timeline.timelines, lib->timeline.nr_timelines,
&ctx, root, key_stack);
key_stack->count--;
}
break;
}
case FLAT_LIB_STOP_MOTION: {
int cg_idx = parts_flat_stop_motion_get_cg_lib(f, lib_idx, local);
if (cg_idx >= 0 && (size_t)cg_idx < f->nr_libraries)
render_flat_cg(parts, &f->textures[cg_idx], key, &ctx);
break;
}
case FLAT_LIB_EMITTER:
// cannot happen, handled above
break;
case FLAT_LIB_MEMORY:
// not implemented
break;
}
}
static void render_flat_layer(struct parts *parts, struct parts_flat *f,
struct flat_layer_state *state,
struct flat_timeline *timelines, size_t nr_timelines,
struct flat_draw_ctx *ctx, mat4 root,
struct flat_key_stack *key_stack)
{
// reverse order for correct z-ordering
for (size_t i = nr_timelines; i-- > 0;) {
struct flat_timeline *tl = &timelines[i];
if (tl->type != FLAT_TIMELINE_GRAPHIC)
continue;
int local = state->current_frame - tl->begin_frame;
if (local < 0 || local >= tl->frame_count)
continue;
if (local >= (int)tl->graphic.count)
continue;
render_flat_item(parts, f, state, i, tl, local, ctx, root, key_stack);
}
}
static void parts_render_flat(struct parts *parts, struct parts_flat *f)
{
if (!f->flat || !f->root_state)
return;
struct flat_draw_ctx ctx;
glm_mat4_identity(ctx.matrix);
glm_translate(ctx.matrix, (vec3){ parts->global.pos.x, parts->global.pos.y, 0 });
glm_rotate_z(ctx.matrix, glm_rad(parts->local.rotation.z), ctx.matrix);
glm_scale(ctx.matrix, (vec3){ parts->global.scale.x, parts->global.scale.y, 1.0f });
ctx.alpha = parts->global.alpha / 255.0f;
glm_vec3_zero(ctx.add_color);
glm_vec3_one(ctx.mul_color);
ctx.draw_filter = PARTS_DRAW_FILTER_NORMAL;
struct flat_key_stack key_stack = { .count = 0 };
render_flat_layer(parts, f, f->root_state,
f->flat->timelines, f->flat->nr_timelines,
&ctx, ctx.matrix, &key_stack);
}
static void parts_render_3dlayer(struct parts *parts, struct parts_3dlayer *l)
{
if (l->sprite_no < 0)
return;
struct sact_sprite *sp = sact_try_get_sprite(l->sprite_no);
if (!sp)
return;
struct texture *tex = sprite_get_texture(sp);
if (!tex->handle)
return;
// The bound sprite's texture is recreated by RE_set_viewport, so
// refresh the parts_common's view of it each frame.
l->common.texture = *tex;
l->common.w = tex->w;
l->common.h = tex->h;
parts_render_cg(parts, &l->common);
}
static void parts_render_flash_shape(struct parts *parts, struct parts_flash *f, struct parts_flash_object *obj, struct swf_tag_define_shape *tag)
{
struct texture *src = ht_get_int(f->bitmaps, tag->fill_style.bitmap_id, NULL);
@@ -165,14 +522,15 @@ static void parts_render_flash_shape(struct parts *parts, struct parts_flash *f,
(parts->global.multiply_color.g / 255.0f) * fixed16_to_float(obj->color_transform.mult_terms[1]),
(parts->global.multiply_color.b / 255.0f) * fixed16_to_float(obj->color_transform.mult_terms[2])
};
parts_render_texture(src, mw_transform, &r, blend_rate, add_color, multiply_color);
parts_render_texture(src, mw_transform, &r, blend_rate, add_color, multiply_color,
0, parts->global.alpha_clipper_parts_no);
}
static void parts_render_flash_sprite(struct parts *parts, struct parts_flash *f, struct parts_flash_object *obj, struct swf_tag_define_sprite *tag)
{
struct parts_flash_object *obj2 = ht_get_int(f->sprites, tag->sprite_id, NULL);
if (!obj)
ERROR("undefined sprite id %d", tag->sprite_id);
if (!obj2)
return; // sprite has no visual elements.
struct swf_tag *child = ht_get_int(f->dictionary, obj2->character_id, NULL);
if (!child)
@@ -239,8 +597,7 @@ void parts_render(struct parts *parts)
return;
if (parts->linked_to >= 0) {
struct parts *link_parts = parts_get(parts->linked_to);
struct parts_state *link_state = &link_parts->states[link_parts->state];
if (!SDL_PointInRect(&parts_prev_pos, &link_state->common.hitbox))
if (!link_parts->is_hovered)
return;
}
@@ -248,6 +605,8 @@ void parts_render(struct parts *parts)
struct parts_state *state = &parts->states[parts->state];
switch (state->type) {
case PARTS_UNINITIALIZED:
case PARTS_RECT_DETECTION:
case PARTS_LAYOUT_BOX:
break;
case PARTS_CG:
case PARTS_ANIMATION:
@@ -255,6 +614,7 @@ void parts_render(struct parts *parts)
case PARTS_HGAUGE:
case PARTS_VGAUGE:
case PARTS_CONSTRUCTION_PROCESS:
case PARTS_MOVIE:
if (state->common.texture.handle)
parts_render_cg(parts, &state->common);
break;
@@ -264,6 +624,12 @@ void parts_render(struct parts *parts)
case PARTS_FLASH:
parts_render_flash(parts, &state->flash);
break;
case PARTS_FLAT:
parts_render_flat(parts, &state->flat);
break;
case PARTS_3DLAYER:
parts_render_3dlayer(parts, &state->layer3d);
break;
}
}
@@ -292,14 +658,8 @@ void parts_sprite_render(struct sprite *sp)
static bool pe_dirty = false;
void parts_render_update(int passed_time)
void parts_render_update(void)
{
// XXX: hack for Rance 01 load issue
// There is a bug in Rance 01 where a single bad frame is displayed after
// loading a save. When this happens, the game passes a negative time delta
// to PE_Update. We fix this issue by ignoring such calls.
if (passed_time < 0)
return;
if (pe_dirty) {
struct parts *p;
PARTS_LIST_FOREACH(p) {
@@ -326,10 +686,14 @@ void parts_dirty(possibly_unused struct parts *parts)
void parts_render_init(void)
{
gfx_load_shader(&parts_shader.shader, "shaders/render.v.glsl", "shaders/parts.f.glsl");
gfx_load_shader(&parts_shader.shader, "shaders/parts.v.glsl", "shaders/parts.f.glsl");
parts_shader.blend_rate = glGetUniformLocation(parts_shader.shader.program, "blend_rate");
parts_shader.bot_left = glGetUniformLocation(parts_shader.shader.program, "bot_left");
parts_shader.top_right = glGetUniformLocation(parts_shader.shader.program, "top_right");
parts_shader.add_color = glGetUniformLocation(parts_shader.shader.program, "add_color");
parts_shader.multiply_color = glGetUniformLocation(parts_shader.shader.program, "multiply_color");
parts_shader.draw_filter = glGetUniformLocation(parts_shader.shader.program, "draw_filter");
parts_shader.use_clipper = glGetUniformLocation(parts_shader.shader.program, "use_clipper");
parts_shader.clipper_tex = glGetUniformLocation(parts_shader.shader.program, "clipper_tex");
parts_shader.inv_clipper_transform = glGetUniformLocation(parts_shader.shader.program, "inv_clipper_transform");
}
+170 -15
View File
@@ -16,13 +16,16 @@
#include "system4.h"
#include "system4/string.h"
#include "system4/archive.h"
#include "vm/page.h"
#include "asset_manager.h"
#include "parts.h"
#include "reign.h"
#include "parts_internal.h"
#include "../hll/iarray.h"
#define CURRENT_SAVE_VERSION 2
#define CURRENT_SAVE_VERSION 7
static void save_parts_params(struct iarray_writer *w, struct parts_params *params)
{
@@ -37,9 +40,10 @@ static void save_parts_params(struct iarray_writer *w, struct parts_params *para
iarray_write_float(w, params->rotation.z);
iarray_write_color(w, &params->add_color);
iarray_write_color(w, &params->multiply_color);
iarray_write(w, params->alpha_clipper_parts_no);
}
static void load_parts_params(struct iarray_reader *r, struct parts_params *params)
static void load_parts_params(struct iarray_reader *r, struct parts_params *params, int version)
{
params->z = iarray_read(r);
iarray_read_point(r, &params->pos);
@@ -52,6 +56,8 @@ static void load_parts_params(struct iarray_reader *r, struct parts_params *para
params->rotation.z = iarray_read_float(r);
iarray_read_color(r, &params->add_color);
iarray_read_color(r, &params->multiply_color);
if (version >= 7)
params->alpha_clipper_parts_no = iarray_read(r);
}
static void save_parts_cg(struct iarray_writer *w, struct parts_cg *cg)
@@ -66,6 +72,7 @@ static void load_parts_cg(struct iarray_reader *r, struct parts *parts, struct p
struct string *name = iarray_read_string_or_null(r);
if (name) {
parts_cg_set(parts, cg, name);
free_string(name);
} else if (no) {
parts_cg_set_by_index(parts, cg, no);
}
@@ -97,9 +104,15 @@ static void load_parts_text(struct iarray_reader *r, struct parts *parts,
text->cursor.y = 0;
int nr_lines = iarray_read(r);
for (unsigned i = 0; i < nr_lines; i++) {
for (int i = 0; i < nr_lines; i++) {
struct string *line = iarray_read_string(r);
if (i > 0) {
struct string *nl = make_string("\n", 1);
parts_text_append(parts, text, nl);
free_string(nl);
}
parts_text_append(parts, text, line);
free_string(line);
}
}
@@ -186,11 +199,12 @@ static void save_parts_cp_op(struct iarray_writer *w, struct parts_cp_op *op)
iarray_write(w, op->create.h);
break;
case PARTS_CP_CG:
iarray_write(w, op->cg.no);
iarray_write_string(w, op->cg.name);
break;
case PARTS_CP_FILL:
case PARTS_CP_FILL_ALPHA_COLOR:
case PARTS_CP_FILL_AMAP:
case PARTS_CP_FILL_WITH_ALPHA:
case PARTS_CP_DRAW_RECT:
iarray_write(w, op->fill.x);
iarray_write(w, op->fill.y);
@@ -203,7 +217,7 @@ static void save_parts_cp_op(struct iarray_writer *w, struct parts_cp_op *op)
break;
case PARTS_CP_DRAW_CUT_CG:
case PARTS_CP_COPY_CUT_CG:
iarray_write(w, op->cut_cg.cg_no);
iarray_write_string(w, op->cut_cg.cg_name);
iarray_write(w, op->cut_cg.dx);
iarray_write(w, op->cut_cg.dy);
iarray_write(w, op->cut_cg.dw);
@@ -222,10 +236,34 @@ static void save_parts_cp_op(struct iarray_writer *w, struct parts_cp_op *op)
iarray_write(w, op->text.line_space);
iarray_write_text_style(w, &op->text.style);
break;
case PARTS_CP_GRAY_FILTER:
iarray_write(w, op->filter.x);
iarray_write(w, op->filter.y);
iarray_write(w, op->filter.w);
iarray_write(w, op->filter.h);
iarray_write(w, op->filter.full_size);
break;
}
}
static struct parts_cp_op *load_parts_cp_op(struct iarray_reader *r)
static struct string *load_cp_cg_name(struct iarray_reader *r, int version)
{
if (version >= 5)
return iarray_read_string(r);
// In version 4 and earlier, the CG name was stored as an integer ID.
// Convert it to a string name by looking up the asset.
int cg_no = iarray_read(r);
if (cg_no <= 0)
return string_ref(&EMPTY_STRING);
struct archive_data *data = asset_get(ASSET_CG, cg_no);
if (!data)
return string_ref(&EMPTY_STRING);
struct string *name = cstr_to_string(data->name);
archive_free_data(data);
return name;
}
static struct parts_cp_op *load_parts_cp_op(struct iarray_reader *r, int version)
{
struct parts_cp_op *op = xcalloc(1, sizeof(struct parts_cp_op));
op->type = iarray_read(r);
@@ -236,11 +274,12 @@ static struct parts_cp_op *load_parts_cp_op(struct iarray_reader *r)
op->create.h = iarray_read(r);
break;
case PARTS_CP_CG:
op->cg.no = iarray_read(r);
op->cg.name = load_cp_cg_name(r, version);
break;
case PARTS_CP_FILL:
case PARTS_CP_FILL_ALPHA_COLOR:
case PARTS_CP_FILL_AMAP:
case PARTS_CP_FILL_WITH_ALPHA:
case PARTS_CP_DRAW_RECT:
op->fill.x = iarray_read(r);
op->fill.y = iarray_read(r);
@@ -253,7 +292,7 @@ static struct parts_cp_op *load_parts_cp_op(struct iarray_reader *r)
break;
case PARTS_CP_DRAW_CUT_CG:
case PARTS_CP_COPY_CUT_CG:
op->cut_cg.cg_no = iarray_read(r);
op->cut_cg.cg_name = load_cp_cg_name(r, version);
op->cut_cg.dx = iarray_read(r);
op->cut_cg.dy = iarray_read(r);
op->cut_cg.dw = iarray_read(r);
@@ -272,6 +311,13 @@ static struct parts_cp_op *load_parts_cp_op(struct iarray_reader *r)
op->text.line_space = iarray_read(r);
iarray_read_text_style(r, &op->text.style);
break;
case PARTS_CP_GRAY_FILTER:
op->filter.x = iarray_read(r);
op->filter.y = iarray_read(r);
op->filter.w = iarray_read(r);
op->filter.h = iarray_read(r);
op->filter.full_size = !!iarray_read(r);
break;
}
return op;
}
@@ -293,11 +339,11 @@ static void save_parts_construction_process(struct iarray_writer *w,
}
static void load_parts_construction_process(struct iarray_reader *r, struct parts *parts,
struct parts_construction_process *cproc)
struct parts_construction_process *cproc, int version)
{
int ops_count = iarray_read(r);
for (int i = 0; i < ops_count; i++) {
struct parts_cp_op *op = load_parts_cp_op(r);
struct parts_cp_op *op = load_parts_cp_op(r, version);
parts_add_cp_op(cproc, op);
}
parts_build_construction_process(parts, cproc);
@@ -320,6 +366,62 @@ static void load_parts_flash(struct iarray_reader *r, struct parts *parts,
parts_flash_seek(flash, iarray_read(r));
}
static void save_parts_flat(struct iarray_writer *w, struct parts_flat *flat)
{
iarray_write_string_or_null(w, flat->name);
}
static void load_parts_flat(struct iarray_reader *r, struct parts *parts,
struct parts_flat *flat)
{
struct string *name = iarray_read_string_or_null(r);
parts_flat_load(parts, flat, name);
free_string(name);
flat->needs_advance = true;
}
static void save_parts_3dlayer(struct iarray_writer *w, struct parts_3dlayer *l)
{
struct RE_plugin *plugin = l->plugin >= 0 ? RE_get_plugin(l->plugin) : NULL;
iarray_write(w, !!plugin);
if (plugin)
RE_plugin_serialize(plugin, w);
}
static void load_parts_3dlayer(struct iarray_reader *r, struct parts_3dlayer *l,
int version)
{
if (!iarray_read(r))
return;
if (!parts_3dlayer_create_plugin(l))
VM_ERROR("cannot create 3D layer plugin");
RE_plugin_deserialize(RE_get_plugin(l->plugin), r, version);
}
static void save_parts_layout_box(struct iarray_writer *w, struct parts_layout_box *lb)
{
iarray_write(w, lb->layout_type);
iarray_write(w, lb->wrap);
iarray_write(w, lb->wrap_size);
iarray_write(w, lb->align);
iarray_write(w, lb->padding_top);
iarray_write(w, lb->padding_bottom);
iarray_write(w, lb->padding_left);
iarray_write(w, lb->padding_right);
}
static void load_parts_layout_box(struct iarray_reader *r, struct parts_layout_box *lb)
{
lb->layout_type = iarray_read(r);
lb->wrap = iarray_read(r);
lb->wrap_size = iarray_read(r);
lb->align = iarray_read(r);
lb->padding_top = iarray_read(r);
lb->padding_bottom = iarray_read(r);
lb->padding_left = iarray_read(r);
lb->padding_right = iarray_read(r);
}
static void save_parts_state(struct iarray_writer *w, struct parts_state *state)
{
iarray_write(w, state->type);
@@ -330,6 +432,11 @@ static void save_parts_state(struct iarray_writer *w, struct parts_state *state)
iarray_write_rectangle(w, &state->common.surface_area);
switch (state->type) {
case PARTS_UNINITIALIZED:
case PARTS_MOVIE:
case PARTS_RECT_DETECTION:
break;
case PARTS_3DLAYER:
save_parts_3dlayer(w, &state->layer3d);
break;
case PARTS_CG:
save_parts_cg(w, &state->cg);
@@ -353,11 +460,17 @@ static void save_parts_state(struct iarray_writer *w, struct parts_state *state)
case PARTS_FLASH:
save_parts_flash(w, &state->flash);
break;
case PARTS_FLAT:
save_parts_flat(w, &state->flat);
break;
case PARTS_LAYOUT_BOX:
save_parts_layout_box(w, &state->layout_box);
break;
}
}
static void load_parts_state(struct iarray_reader *r, struct parts *parts,
struct parts_state *state)
struct parts_state *state, int version)
{
parts_state_reset(state, iarray_read(r));
state->common.w = iarray_read(r);
@@ -367,6 +480,12 @@ static void load_parts_state(struct iarray_reader *r, struct parts *parts,
iarray_read_rectangle(r, &state->common.surface_area);
switch (state->type) {
case PARTS_UNINITIALIZED:
case PARTS_MOVIE:
case PARTS_RECT_DETECTION:
break;
case PARTS_3DLAYER:
if (version > 5)
load_parts_3dlayer(r, &state->layer3d, version);
break;
case PARTS_CG:
load_parts_cg(r, parts, &state->cg);
@@ -387,11 +506,17 @@ static void load_parts_state(struct iarray_reader *r, struct parts *parts,
load_parts_gauge(r, parts, &state->gauge, true);
break;
case PARTS_CONSTRUCTION_PROCESS:
load_parts_construction_process(r, parts, &state->cproc);
load_parts_construction_process(r, parts, &state->cproc, version);
break;
case PARTS_FLASH:
load_parts_flash(r, parts, &state->flash);
break;
case PARTS_FLAT:
load_parts_flat(r, parts, &state->flat);
break;
case PARTS_LAYOUT_BOX:
load_parts_layout_box(r, &state->layout_box);
break;
}
}
@@ -489,6 +614,14 @@ static void save_parts(struct iarray_writer *w, struct parts *parts)
iarray_write(w, parts->linked_from);
iarray_write(w, parts->draw_filter);
iarray_write(w, parts->message_window);
iarray_write(w, parts->controller_no);
iarray_write(w, parts->pass_cursor);
iarray_write(w, parts->lock_input_state);
iarray_write(w, parts->margin_top);
iarray_write(w, parts->margin_bottom);
iarray_write(w, parts->margin_left);
iarray_write(w, parts->margin_right);
iarray_write(w, parts->draggable);
unsigned motion_count_pos = iarray_writer_pos(w);
iarray_write(w, 0); // size of motion list
@@ -509,11 +642,11 @@ static void load_parts(struct iarray_reader *r, int version)
struct parts *parts = parts_get(no);
parts->state = iarray_read(r);
for (int i = 0; i < PARTS_NR_STATES; i++) {
load_parts_state(r, parts, &parts->states[i]);
load_parts_state(r, parts, &parts->states[i], version);
}
load_parts_params(r, &parts->local);
load_parts_params(r, &parts->global);
load_parts_params(r, &parts->local, version);
load_parts_params(r, &parts->global, version);
parts->pending_parent = iarray_read(r);
parts->delegate_index = iarray_read(r);
parts->sprite_deform = iarray_read(r);
@@ -526,6 +659,18 @@ static void load_parts(struct iarray_reader *r, int version)
parts->draw_filter = iarray_read(r);
if (version > 0)
parts->message_window = iarray_read(r);
if (version > 2 && version < 7)
parts->local.alpha_clipper_parts_no = iarray_read(r);
if (version > 3 || (version == 3 && parts_multi_controller)) {
parts->controller_no = iarray_read(r);
parts->pass_cursor = iarray_read(r);
parts->lock_input_state = iarray_read(r);
parts->margin_top = iarray_read(r);
parts->margin_bottom = iarray_read(r);
parts->margin_left = iarray_read(r);
parts->margin_right = iarray_read(r);
parts->draggable = iarray_read(r);
}
int motion_count = iarray_read(r);
for (int i = 0; i < motion_count; i++) {
@@ -577,6 +722,9 @@ static bool parts_engine_save(struct page **buffer, bool save_hidden)
if (CURRENT_SAVE_VERSION > 1)
save_numeral_fonts(&w);
iarray_write(&w, ctrl_stack.active);
iarray_write(&w, ctrl_stack.nr_controllers);
unsigned count_pos = iarray_writer_pos(&w);
iarray_write(&w, 0); // size of parts list
@@ -585,6 +733,8 @@ static bool parts_engine_save(struct page **buffer, bool save_hidden)
PARTS_LIST_FOREACH(parts) {
if (!save_hidden && !parts->global.show)
continue;
if (!parts->want_save)
continue;
save_parts(&w, parts);
count++;
}
@@ -634,6 +784,11 @@ bool PE_Load(struct page **buffer)
if (version > 1)
load_numeral_fonts(&r);
if (version > 3 || (version == 3 && parts_multi_controller)) {
ctrl_stack.active = iarray_read(&r);
ctrl_stack.nr_controllers = iarray_read(&r);
}
int nr_parts = iarray_read(&r);
if (nr_parts < 0) {
WARNING("invalid parts count");
+86 -12
View File
@@ -15,6 +15,7 @@
*/
#include <math.h>
#include <string.h>
#include "system4.h"
#include "system4/string.h"
@@ -71,6 +72,7 @@ static const char *parts_text_append_char(struct parts_text *t, const char *str)
if (*str == '\n') {
t->lines = xrealloc_array(t->lines, t->nr_lines, t->nr_lines + 1,
sizeof(struct parts_text_line));
t->lines[t->nr_lines].height = text_style_height(&t->ts);
t->nr_lines++;
return str + 1;
}
@@ -82,11 +84,10 @@ static const char *parts_text_append_char(struct parts_text *t, const char *str)
ch->off = text_style_offset(&t->ts);
int len = extract_sjis_char(str, ch->ch);
int width = ceilf(text_style_width(&t->ts, ch->ch));
int width = gfx_size_char(&t->ts, ch->ch);
int height = text_style_height(&t->ts);
gfx_init_texture_rgba(&ch->t, width, height, (SDL_Color){0,0,0,0});
ch->advance = gfx_render_textf(&ch->t, 0, 0, ch->ch, &t->ts, false);
ch->advance = ceilf(gfx_render_textf(&ch->t, 0, 0, ch->ch, &t->ts, false));
line->width += ch->advance;
line->height = max(line->height, height);
return str + len;
@@ -109,6 +110,8 @@ void parts_text_append(struct parts *parts, struct parts_text *t, struct string
int height = 0;
for (int i = 0; i < t->nr_lines; i++) {
f_width = max(f_width, t->lines[i].width);
if (i > 0)
height += t->line_space;
height += t->lines[i].height;
}
int width = ceilf(f_width);
@@ -131,6 +134,20 @@ void parts_text_free(struct parts_text *t)
free(t->lines);
}
static void parts_text_rerender(struct parts *parts, struct parts_text *t)
{
if (!t->nr_lines)
return;
struct string *text = parts_text_get(t);
parts_text_free(t);
t->ts.font_size = NULL; // clear cached font
t->lines = NULL;
t->nr_lines = 0;
parts_text_append(parts, t, text);
free_string(text);
parts_dirty(parts);
}
static void parts_text_clear(struct parts *parts, int state)
{
struct parts_text *text = parts_get_text(parts, state);
@@ -152,6 +169,14 @@ bool PE_SetText(int parts_no, struct string *text, int state)
return true;
}
struct string *PE_GetText(int parts_no, int state)
{
if (!parts_state_valid(--state))
return NULL;
return parts_text_get(parts_get_text(parts_get(parts_no), state));
}
bool PE_AddPartsText(int parts_no, struct string *text, int state)
{
if (!parts_state_valid(--state))
@@ -180,13 +205,16 @@ bool PE_SetFont(int parts_no, int type, int size, int r, int g, int b, float bol
if (!parts_state_valid(--state))
return false;
struct parts_text *text = parts_get_text(parts_get(parts_no), state);
struct parts *parts = parts_get(parts_no);
struct parts_text *text = parts_get_text(parts, state);
text->ts.face = type;
text->ts.size = size;
text->ts.color = (SDL_Color) { r, g, b, 255 };
text->ts.weight = bold_weight * 1000;
text->ts.edge_color = (SDL_Color) { edge_r, edge_g, edge_b, 255 };
text_style_set_edge_width(&text->ts, edge_weight);
text->ts.bold_width = bold_weight;
parts_text_rerender(parts, text);
return true;
}
@@ -195,7 +223,12 @@ bool PE_SetPartsFontType(int parts_no, int type, int state)
if (!parts_state_valid(--state))
return false;
parts_get_text(parts_get(parts_no), state)->ts.face = type;
struct parts *parts = parts_get(parts_no);
struct parts_text *text = parts_get_text(parts, state);
if (text->ts.face == type)
return true;
text->ts.face = type;
parts_text_rerender(parts, text);
return true;
}
@@ -204,7 +237,12 @@ bool PE_SetPartsFontSize(int parts_no, int size, int state)
if (!parts_state_valid(--state))
return false;
parts_get_text(parts_get(parts_no), state)->ts.size = size;
struct parts *parts = parts_get(parts_no);
struct parts_text *text = parts_get_text(parts, state);
if (text->ts.size == size)
return true;
text->ts.size = size;
parts_text_rerender(parts, text);
return true;
}
@@ -213,7 +251,13 @@ bool PE_SetPartsFontColor(int parts_no, int r, int g, int b, int state)
if (!parts_state_valid(--state))
return false;
parts_get_text(parts_get(parts_no), state)->ts.color = (SDL_Color) { r, g, b, 255 };
struct parts *parts = parts_get(parts_no);
struct parts_text *text = parts_get_text(parts, state);
SDL_Color color = { r, g, b, 255 };
if (!memcmp(&text->ts.color, &color, sizeof(SDL_Color)))
return true;
text->ts.color = color;
parts_text_rerender(parts, text);
return true;
}
@@ -222,7 +266,12 @@ bool PE_SetPartsFontBoldWeight(int parts_no, float bold_weight, int state)
if (!parts_state_valid(--state))
return false;
parts_get_text(parts_get(parts_no), state)->ts.bold_width = bold_weight;
struct parts *parts = parts_get(parts_no);
struct parts_text *text = parts_get_text(parts, state);
if (text->ts.bold_width == bold_weight)
return true;
text->ts.bold_width = bold_weight;
parts_text_rerender(parts, text);
return true;
}
@@ -231,7 +280,13 @@ bool PE_SetPartsFontEdgeColor(int parts_no, int r, int g, int b, int state)
if (!parts_state_valid(--state))
return false;
parts_get_text(parts_get(parts_no), state)->ts.edge_color = (SDL_Color) { r, g, b, 255 };
struct parts *parts = parts_get(parts_no);
struct parts_text *text = parts_get_text(parts, state);
SDL_Color color = { r, g, b, 255 };
if (!memcmp(&text->ts.edge_color, &color, sizeof(SDL_Color)))
return true;
text->ts.edge_color = color;
parts_text_rerender(parts, text);
return true;
}
@@ -240,8 +295,17 @@ bool PE_SetPartsFontEdgeWeight(int parts_no, float edge_weight, int state)
if (!parts_state_valid(--state))
return false;
struct parts_text *text = parts_get_text(parts_get(parts_no), state);
struct parts *parts = parts_get(parts_no);
struct parts_text *text = parts_get_text(parts, state);
float old_left = text->ts.edge_left;
float old_up = text->ts.edge_up;
float old_right = text->ts.edge_right;
float old_down = text->ts.edge_down;
text_style_set_edge_width(&text->ts, edge_weight);
if (text->ts.edge_left == old_left && text->ts.edge_up == old_up &&
text->ts.edge_right == old_right && text->ts.edge_down == old_down)
return true;
parts_text_rerender(parts, text);
return true;
}
@@ -250,7 +314,12 @@ bool PE_SetTextCharSpace(int parts_no, int char_space, int state)
if (!parts_state_valid(--state))
return false;
parts_get_text(parts_get(parts_no), state)->ts.font_spacing = char_space;
struct parts *parts = parts_get(parts_no);
struct parts_text *text = parts_get_text(parts, state);
if (text->ts.font_spacing == char_space)
return true;
text->ts.font_spacing = char_space;
parts_text_rerender(parts, text);
return true;
}
@@ -259,7 +328,12 @@ bool PE_SetTextLineSpace(int parts_no, int line_space, int state)
if (!parts_state_valid(--state))
return false;
parts_get_text(parts_get(parts_no), state)->line_space = line_space;
struct parts *parts = parts_get(parts_no);
struct parts_text *text = parts_get_text(parts, state);
if (text->line_space == line_space)
return true;
text->line_space = line_space;
parts_text_rerender(parts, text);
return true;
}
+16 -15
View File
@@ -194,16 +194,15 @@ static int get_gsave_version(void)
int save_globals(const char *keyname, const char *filename, const char *group_name, int *n_out)
{
int group = -1;
int nr_vars;
int nr_vars = 0;
if (group_name) {
if ((group = get_group_index(group_name)) < 0) {
WARNING("Unregistered global group: %s", display_sjis0(group_name));
return 0;
}
nr_vars = 0;
for (int i = 0; i < ain->nr_globals; i++) {
if (ain->globals[i].group_index == group)
nr_vars++;
} else {
for (int i = 0; i < ain->nr_globals; i++) {
if (ain->globals[i].group_index == group)
nr_vars++;
}
}
} else {
nr_vars = ain->nr_globals;
@@ -212,14 +211,16 @@ int save_globals(const char *keyname, const char *filename, const char *group_na
struct gsave *save = gsave_create(get_gsave_version(), keyname, ain->nr_globals, group_name);
gsave_add_globals_record(save, nr_vars);
struct gsave_global *global = save->globals;
for (int i = 0; i < ain->nr_globals; i++) {
if (group >= 0 && ain->globals[i].group_index != group)
continue;
global->name = strdup(ain->globals[i].name);
global->type = ain->globals[i].type.data;
global->value = add_value_to_gsave(global->type, global_get(i), save);
global++;
if (nr_vars > 0) {
struct gsave_global *global = save->globals;
for (int i = 0; i < ain->nr_globals; i++) {
if (group >= 0 && ain->globals[i].group_index != group)
continue;
global->name = strdup(ain->globals[i].name);
global->type = ain->globals[i].type.data;
global->value = add_value_to_gsave(global->type, global_get(i), save);
global++;
}
}
char *path = savedir_path(filename);
+4 -1
View File
@@ -141,13 +141,16 @@ void sprite_init_chipmunk(void)
return;
}
}
gfx_load_shader(&sprite_shader.s, "shaders/render.v.glsl", "shaders/parts.f.glsl");
gfx_load_shader(&sprite_shader.s, "shaders/parts.v.glsl", "shaders/parts.f.glsl");
sprite_shader.blend_rate = glGetUniformLocation(sprite_shader.s.program, "blend_rate");
sprite_shader.multiply_color = glGetUniformLocation(sprite_shader.s.program, "multiply_color");
sprite_shader.add_color = glGetUniformLocation(sprite_shader.s.program, "add_color");
sprite_shader.bot_left = glGetUniformLocation(sprite_shader.s.program, "bot_left");
sprite_shader.top_right = glGetUniformLocation(sprite_shader.s.program, "top_right");
sprite_shader.s.prepare = prepare_chipmunk_shader;
glUseProgram(sprite_shader.s.program);
glUniform1i(glGetUniformLocation(sprite_shader.s.program, "use_clipper"), 0);
glUseProgram(0);
}
static void sprite_render(struct sprite *_sp)
+6 -7
View File
@@ -19,10 +19,10 @@
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <zlib.h>
#include "system4.h"
#include "system4/buffer.h"
#include "system4/little_endian.h"
#include "system4/zlib.h"
#include "swf.h"
static void free_tag_list(struct swf_tag *tags);
@@ -277,10 +277,9 @@ static struct swf_tag_define_bits_lossless *parse_define_bits_lossless(struct bu
if (tag->format != 5)
ERROR("DefineBitsLossless: unsupported format %d", tag->format);
unsigned long len = 4 * tag->width * tag->height;
size_t len = 4 * tag->width * tag->height;
uint8_t *pixels = xmalloc(len);
int rv = uncompress(pixels, &len, r->buf + r->index, buffer_remaining(r));
if (rv != Z_OK || len != 4 * tag->width * tag->height)
if (!zlib_decompress_exact(pixels, len, r->buf + r->index, buffer_remaining(r)))
ERROR("DefineBitsLossless: broken bitmap data");
uint8_t alpha_const = type == TAG_DEFINE_BITS_LOSSLESS ? 0xff : 0;
@@ -666,10 +665,10 @@ struct swf *aff_load(uint8_t *data, size_t size)
uint8_t *raw = xmalloc(raw_size);
memcpy(raw, buf, 8);
raw[0] = 'F';
unsigned long dest_size = raw_size - 8;
int rv = uncompress(raw + 8, &dest_size, buf + 8, size - 8);
size_t dest_size = raw_size - 8;
bool ok = zlib_decompress_exact(raw + 8, dest_size, buf + 8, size - 8);
free(buf);
if (rv != Z_OK || dest_size != raw_size - 8) {
if (!ok) {
free(raw);
return NULL;
}
+19 -8
View File
@@ -158,7 +158,16 @@ static bool read_config(const char *path)
return true;
}
static void read_user_config_file(const char *path)
static char *user_config_path(struct ini_entry *entry, const char *basedir)
{
const char *path = ini_string(entry)->text;
if (basedir && !is_absolute_path(path)) {
return path_join(basedir, path);
}
return xstrdup(path);
}
static void read_user_config_file(const char *path, const char *basedir)
{
int ini_size;
struct ini_entry *ini = ini_parse(path, &ini_size);
@@ -167,11 +176,14 @@ static void read_user_config_file(const char *path)
for (int i = 0; i < ini_size; i++) {
if (!strcmp(ini[i].name->text, "font-mincho")) {
config.font_paths[FONT_MINCHO] = xstrdup(ini_string(&ini[i])->text);
free(config.font_paths[FONT_MINCHO]);
config.font_paths[FONT_MINCHO] = user_config_path(&ini[i], basedir);
} else if (!strcmp(ini[i].name->text, "font-gothic")) {
config.font_paths[FONT_GOTHIC] = xstrdup(ini_string(&ini[i])->text);
free(config.font_paths[FONT_GOTHIC]);
config.font_paths[FONT_GOTHIC] = user_config_path(&ini[i], basedir);
} else if (!strcmp(ini[i].name->text, "font-fnl")) {
config.fnl_path = xstrdup(ini_string(&ini[i])->text);
free(config.fnl_path);
config.fnl_path = user_config_path(&ini[i], basedir);
} else if (!strcmp(ini[i].name->text, "font-x-scale")) {
float f = strtof(ini_string(&ini[i])->text, NULL);
if (fabsf(f) < 0.01) {
@@ -190,7 +202,7 @@ static void read_user_config_file(const char *path)
}
} else if (!strcmp(ini[i].name->text, "save-folder")) {
free(config.save_dir);
config.save_dir = xstrdup(ini_string(&ini[i])->text);
config.save_dir = user_config_path(&ini[i], basedir);
} else if (!strcmp(ini[i].name->text, "save-format")) {
if (!strcmp(ini_string(&ini[i])->text, "json")) {
config.save_format = SAVE_FORMAT_JSON;
@@ -213,12 +225,12 @@ static void read_user_config(void)
// global config
path = xmalloc(PATH_MAX);
snprintf(path, PATH_MAX-1, "%s/.xsys4rc", config.home_dir);
read_user_config_file(path);
read_user_config_file(path, NULL);
free(path);
// game-specific config
path = gamedir_path(".xsys4rc");
read_user_config_file(path);
read_user_config_file(path, config.game_dir);
free(path);
}
@@ -419,7 +431,6 @@ int main(int argc, char *argv[])
{
sys_error_handler = error_handler;
initialize_instructions();
char *ainfile;
int err = AIN_SUCCESS;
bool audit = false;
+8 -6
View File
@@ -158,23 +158,25 @@ static uint32_t char_to_code(const char *ch, enum charmap charmap)
return c;
}
float gfx_size_char(struct text_style *ts, const char *ch)
float gfx_size_char_unstyled(struct text_style *ts, const char *ch)
{
struct font_size *size = text_style_font_size(ts);
return font_size_char(size, char_to_code(ch, size->font->charmap));
}
float gfx_size_char_kerning(struct text_style *ts, uint32_t code, uint32_t code_next)
float gfx_size_char(struct text_style *ts, const char *ch)
{
struct font_size *size = text_style_font_size(ts);
return size->font->size_char_kerning(size, code, code_next);
float w = gfx_size_char_unstyled(ts, ch);
if (gfx_text_advance_edges)
w += ts->edge_left + ts->edge_right + ceilf(ts->bold_width)*2;
return w * ts->scale_x;
}
float gfx_size_text(struct text_style *ts, const char *text)
{
struct font_size *size = text_style_font_size(ts);
float edge_advance = gfx_text_advance_edges
? ts->edge_left + ts->edge_right + ceilf(ts->bold_width)
? ts->edge_left + ts->edge_right + ceilf(ts->bold_width)*2
: 0.0f;
float x = 0.0f;
while (*text) {
@@ -182,7 +184,7 @@ float gfx_size_text(struct text_style *ts, const char *text)
x += edge_advance;
text = sjis_skip_char(text);
}
return x;
return x * ts->scale_x;;
}
float gfx_get_actual_font_size(unsigned face, float size)
+21 -8
View File
@@ -243,19 +243,12 @@ int gfx_init(void)
#endif
sdl.format = SDL_AllocFormat(SDL_PIXELFORMAT_RGBA32);
uint32_t window_flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN;
#ifdef __ANDROID__
window_flags |= SDL_WINDOW_FULLSCREEN;
SDL_SetHint(SDL_HINT_ORIENTATIONS, "LandscapeLeft LandscapeRight");
#else
window_flags |= SDL_WINDOW_RESIZABLE;
#endif
sdl.window = SDL_CreateWindow("XSystem4",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
config.view_width,
config.view_height,
window_flags);
SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE);
if (!sdl.window)
ERROR("SDL_CreateWindow failed: %s", SDL_GetError());
@@ -326,6 +319,26 @@ void gfx_set_window_logical_size(int w, int h)
main_surface_init(w, h);
}
bool gfx_set_fullscreen(bool enable)
{
uint32_t flags = enable ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0;
if (SDL_SetWindowFullscreen(sdl.window, flags) < 0) {
WARNING("Failed to set fullscreen mode: %s", SDL_GetError());
return false;
}
return true;
}
bool gfx_is_fullscreen(void)
{
return SDL_GetWindowFlags(sdl.window) & SDL_WINDOW_FULLSCREEN_DESKTOP;
}
void gfx_toggle_fullscreen(void)
{
gfx_set_fullscreen(!gfx_is_fullscreen());
}
void gfx_update_screen_scale(void)
{
int display_w, display_h;

Some files were not shown because too many files have changed in this diff Show More