Compare commits
252 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
461584b674 | ||
|
|
1d71be674d | ||
|
|
45b2275b68 | ||
|
|
2e6e033f1f | ||
|
|
25b706789d | ||
|
|
37a29440de | ||
|
|
0bc140b4e8 | ||
|
|
b292d227f9 | ||
|
|
0f9ebd56f6 | ||
|
|
fe4820cf65 | ||
|
|
339d8dc79f | ||
|
|
97bf8b3efd | ||
|
|
02799ddc54 | ||
|
|
8e1867d235 | ||
|
|
fd2235d2f6 | ||
|
|
fc5767c96e | ||
|
|
bb82ab69af | ||
|
|
5300846f73 | ||
|
|
b7cc3ec5d6 | ||
|
|
11dafe26ac | ||
|
|
d56528bc5a | ||
|
|
db9879b5b5 | ||
|
|
34889988a9 | ||
|
|
6575fc7f94 | ||
|
|
f76c25614c | ||
|
|
955a0942f1 | ||
|
|
8fb0813cf5 | ||
|
|
1da8e0332e | ||
|
|
628c0c992d | ||
|
|
faa75a4eea | ||
|
|
9a2d128e87 | ||
|
|
34419a589f | ||
|
|
b146bb667b | ||
|
|
e90f23661d | ||
|
|
177a3a9741 | ||
|
|
f095bf7024 | ||
|
|
81e6b7ab31 | ||
|
|
8a9750b0b3 | ||
|
|
ae5369b8fe | ||
|
|
9bffe5c9a9 | ||
|
|
440efe1ce1 | ||
|
|
16b24a0bf9 | ||
|
|
f9ed858549 | ||
|
|
1e1fe85b88 | ||
|
|
0698d27ce9 | ||
|
|
a4e1fab100 | ||
|
|
d22fcb0056 | ||
|
|
d96bc9b1c2 | ||
|
|
5a4856ec82 | ||
|
|
2fb9e6d633 | ||
|
|
35ecbf33ae | ||
|
|
7bd510ee5f | ||
|
|
cf746c597e | ||
|
|
bf66f96844 | ||
|
|
a82bd671de | ||
|
|
687d77e18a | ||
|
|
28ff6c81ef | ||
|
|
9f170f298d | ||
|
|
291edaf4f2 | ||
|
|
43d837571e | ||
|
|
07cab81ba1 | ||
|
|
10e75f18ae | ||
|
|
2d7394f03e | ||
|
|
8bba449196 | ||
|
|
0953a430c4 | ||
|
|
435a9a0a1d | ||
|
|
1cacabdff6 | ||
|
|
184ee719b3 | ||
|
|
09dd996181 | ||
|
|
2c32ff69e3 | ||
|
|
e910aee184 | ||
|
|
6db35b0f95 | ||
|
|
4a5753c068 | ||
|
|
fcb2337fa2 | ||
|
|
f51380c93c | ||
|
|
408c264e85 | ||
|
|
4567a8e1ad | ||
|
|
2fb41e533c | ||
|
|
47b1bac31f | ||
|
|
a4caa44580 | ||
|
|
f3f375cef1 | ||
|
|
1ac55692e9 | ||
|
|
557e591c31 | ||
|
|
1f3d3bffbd | ||
|
|
28043bd7ca | ||
|
|
4183f563ab | ||
|
|
e0cffab768 | ||
|
|
d5f59e904b | ||
|
|
64dd586df8 | ||
|
|
e7eaa82c9d | ||
|
|
ecd98d0626 | ||
|
|
df674a73a6 | ||
|
|
247d1339c0 | ||
|
|
ada2650c50 | ||
|
|
d8acb99665 | ||
|
|
fbf8cba9db | ||
|
|
438df88316 | ||
|
|
73abe5b6ce | ||
|
|
03273de829 | ||
|
|
eb815b7820 | ||
|
|
2c532b5bd8 | ||
|
|
fa52350146 | ||
|
|
dfe6edad88 | ||
|
|
9dc0905c93 | ||
|
|
d32d567c2b | ||
|
|
4af345c567 | ||
|
|
8c00fbdeb1 | ||
|
|
b472ce2bad | ||
|
|
c510e78704 | ||
|
|
b38433d191 | ||
|
|
5a3e891e43 | ||
|
|
b01e9e5f01 | ||
|
|
a083d3de74 | ||
|
|
e45c1eeb22 | ||
|
|
9b568e8e1a | ||
|
|
9c78094399 | ||
|
|
0b848cd72e | ||
|
|
8a4242e81e | ||
|
|
52b1af576f | ||
|
|
517d40c294 | ||
|
|
462c040e9a | ||
|
|
e40f3c989f | ||
|
|
af3d69bcfb | ||
|
|
a5c5f9cb15 | ||
|
|
ab07c9af04 | ||
|
|
09f6c35496 | ||
|
|
138792df48 | ||
|
|
16059ab632 | ||
|
|
d499abe2c4 | ||
|
|
d78e04c6b5 | ||
|
|
5e2bd9a59a | ||
|
|
a1e73be3fc | ||
|
|
06cd13a17f | ||
|
|
72c0a6c4a9 | ||
|
|
e00c51502b | ||
|
|
53d2e53d78 | ||
|
|
f725c5e1b8 | ||
|
|
d6ebda657a | ||
|
|
f77a715845 | ||
|
|
3a6e004fc6 | ||
|
|
b9af95d770 | ||
|
|
597dc62e24 | ||
|
|
0f154741ed | ||
|
|
3071e512c4 | ||
|
|
dbef1dacdc | ||
|
|
6d66ccbb61 | ||
|
|
4e1f418a8d | ||
|
|
fb35397238 | ||
|
|
04ffb0d4dd | ||
|
|
121bbc01d5 | ||
|
|
6dce57e197 | ||
|
|
b5b7c856d6 | ||
|
|
ef048791c1 | ||
|
|
f885fa9164 | ||
|
|
54d955d1e7 | ||
|
|
3c2dd0edba | ||
|
|
7ee07bb34c | ||
|
|
f463ba99a0 | ||
|
|
767e21d5fa | ||
|
|
d108836b6a | ||
|
|
f36fa5e998 | ||
|
|
71de180871 | ||
|
|
8c25089966 | ||
|
|
56c33b2eef | ||
|
|
cf7c9dc7c3 | ||
|
|
d7f64d3570 | ||
|
|
9d1a51884f | ||
|
|
609e1f19b6 | ||
|
|
ce5150bffb | ||
|
|
ce6d2049f8 | ||
|
|
f14f200cb0 | ||
|
|
c4f22be9b9 | ||
|
|
30638b65e2 | ||
|
|
94cb898bb9 | ||
|
|
73ec9a40d3 | ||
|
|
6787fab0c2 | ||
|
|
f752793caa | ||
|
|
9b6c77ea3f | ||
|
|
4b35b2f7d8 | ||
|
|
23a14cb76e | ||
|
|
8ae4968539 | ||
|
|
9ae53bddb8 | ||
|
|
af72fe5749 | ||
|
|
7e17419811 | ||
|
|
fec243b482 | ||
|
|
324478845d | ||
|
|
0dbde481d5 | ||
|
|
bc44de0493 | ||
|
|
f257ec03af | ||
|
|
4e0d72ca8a | ||
|
|
aa5ebd13fc | ||
|
|
cca015238d | ||
|
|
296c489562 | ||
|
|
ae40949008 | ||
|
|
77f76bd4ef | ||
|
|
a0f1ef7780 | ||
|
|
73c2db6b69 | ||
|
|
c529c4be69 | ||
|
|
8cd86f1640 | ||
|
|
46936c1878 | ||
|
|
22ddd83931 | ||
|
|
f7c68934f7 | ||
|
|
4fc0fbf5a8 | ||
|
|
0187b5f78e | ||
|
|
0cfe0e5126 | ||
|
|
b7a94fa085 | ||
|
|
de7c9d4d60 | ||
|
|
c781ee9666 | ||
|
|
1f40279a36 | ||
|
|
a6dd95ab0a | ||
|
|
cc95554db7 | ||
|
|
34d885674c | ||
|
|
99ecd424f4 | ||
|
|
f579c13ceb | ||
|
|
8dbcadac59 | ||
|
|
46944e5699 | ||
|
|
88726cbb39 | ||
|
|
36bcf0fa30 | ||
|
|
b8a441f971 | ||
|
|
56bbc2ff9a | ||
|
|
ebe789aa52 | ||
|
|
daba462575 | ||
|
|
ad9358547d | ||
|
|
f57c37b900 | ||
|
|
7bafc260dd | ||
|
|
3888a198c8 | ||
|
|
e58208e13d | ||
|
|
74739d87ed | ||
|
|
e620697917 | ||
|
|
8d278b0c19 | ||
|
|
26db4e1aa0 | ||
|
|
66c6b91222 | ||
|
|
591485a5ac | ||
|
|
301f5a2a53 | ||
|
|
6e60cf5970 | ||
|
|
a9f44e74d4 | ||
|
|
2c2cb17f74 | ||
|
|
9dc5f7ccc3 | ||
|
|
7c377fca94 | ||
|
|
c4e804234b | ||
|
|
3dc959c17d | ||
|
|
2c5bc3b5dd | ||
|
|
27e91fb4a4 | ||
|
|
2669afbae2 | ||
|
|
a16065439e | ||
|
|
5b76cbfad0 | ||
|
|
074f1956aa | ||
|
|
e8e945b17d | ||
|
|
35c0d383c5 | ||
|
|
b27c94e5bd | ||
|
|
1562e4f9c7 | ||
|
|
c2ef5b15d4 |
56
.clang-format
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
BasedOnStyle: LLVM
|
||||
IndentWidth: 4
|
||||
UseTab: Never
|
||||
|
||||
# Allman brace style
|
||||
BreakBeforeBraces: Allman
|
||||
AllowShortBlocksOnASingleLine: false
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: None
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
|
||||
# Pointer alignment
|
||||
PointerAlignment: Left
|
||||
|
||||
# Column limit
|
||||
ColumnLimit: 100
|
||||
|
||||
# Case labels
|
||||
IndentCaseLabels: true
|
||||
|
||||
# Include sorting
|
||||
SortIncludes: CaseSensitive
|
||||
IncludeBlocks: Regroup
|
||||
|
||||
ContinuationIndentWidth: 4
|
||||
AlignArrayOfStructures: Left
|
||||
PackConstructorInitializers: Never
|
||||
|
||||
SpaceBeforeParens: Custom
|
||||
SpaceBeforeParensOptions:
|
||||
AfterControlStatements: true
|
||||
AfterFunctionDefinitionName: false
|
||||
|
||||
AlignOperands: AlignAfterOperator
|
||||
BreakBeforeBinaryOperators: None
|
||||
|
||||
IndentPPDirectives: None
|
||||
AlignConsecutiveMacros: Consecutive
|
||||
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
|
||||
AllowAllArgumentsOnNextLine: false
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AlignAfterOpenBracket: BlockIndent
|
||||
PenaltyBreakBeforeFirstCallParameter: 0
|
||||
|
||||
ReflowComments: true
|
||||
SpacesInLineCommentPrefix:
|
||||
Minimum: 1
|
||||
Maximum: 1
|
||||
|
||||
PenaltyReturnTypeOnItsOwnLine: 99999999
|
||||
AlignEscapedNewlines: Left
|
||||
|
||||
BracedInitializerIndentWidth: 0
|
||||
31
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: "[BUG]"
|
||||
labels: bug
|
||||
assignees:
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior.
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
Any helpful screenshots or videos
|
||||
|
||||
**What version**
|
||||
- **Nightly**: If you downloaded the ROM you can provide either the ROM name, or the name of the zipfile
|
||||
- **Mainline Release**: Please provide the name of the release (**Joker Build**, **Itemless Build**, etc)
|
||||
- **I Don't Know**: Please try to use the `build_bug_report.py` script in the `scripts/` directory. If there is no output from that, then try to recreate your bug with a new version.
|
||||
|
||||
**What Emulator or Platform were you playing on**
|
||||
- e.g. mgba, flashcart
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
101
.github/workflows/build_ci_workflow.yml
vendored
|
|
@ -2,60 +2,77 @@ name: Build CI
|
|||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
clear-cache:
|
||||
description: 'Clear docker cache before running'
|
||||
type: boolean
|
||||
default: false
|
||||
workflow_call:
|
||||
inputs:
|
||||
upload-build-artifact:
|
||||
required: true
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
clang-format-job:
|
||||
name: Run clang-format test
|
||||
uses: ./.github/workflows/clang_format_ci_workflow.yml
|
||||
|
||||
unit-tests-job:
|
||||
name: Run unit tests
|
||||
uses: ./.github/workflows/unit_tests_ci_workflow.yml
|
||||
|
||||
build:
|
||||
name: Build ROM
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set devkitPro environment variables
|
||||
run: |
|
||||
# Installing to home directory to avoid permission denied on caching
|
||||
echo "DEVKITPRO=/home/runner/devkitpro" >> $GITHUB_ENV
|
||||
echo "DEVKITARM=/home/runner/devkitpro/devkitARM" >> $GITHUB_ENV
|
||||
export PATH=$DEVKITARM/bin:$DEVKITPRO/tools/bin:$PATH
|
||||
|
||||
# Caching to avoid excessive request to the devkitpro server from a runner because they
|
||||
# eventually block and result in permission denied 403.
|
||||
# If that happens it takes like 2 hours until you can make requests again
|
||||
- name: Cache devkitPro toolchain
|
||||
id: cache-devkitpro
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Get docker image cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: /home/runner/devkitpro
|
||||
# Using a manual key version suffix - v{num}, if changes are made that make the
|
||||
# cache stale and require a refresh, just bump the version number so there's
|
||||
# a cache miss. The key needs to match in the "Save devkitPro to cache" stage
|
||||
# so you need to bump the number there too...
|
||||
key: devkitpro-${{ runner.os }}-v1
|
||||
path: /tmp/docker-cache
|
||||
key: docker-images-${{ hashFiles('docker-compose.yml') }}
|
||||
|
||||
- name: Set up devkitARM (if not cached)
|
||||
if: steps.cache-devkitpro.outputs.cache-hit != 'true'
|
||||
- name: Clear docker cache (if requested)
|
||||
if: github.event.inputs.clear-cache == 'true'
|
||||
run: |
|
||||
sudo apt update
|
||||
wget https://apt.devkitpro.org/install-devkitpro-pacman
|
||||
chmod +x install-devkitpro-pacman
|
||||
sudo ./install-devkitpro-pacman
|
||||
mkdir -p "$DEVKITPRO"
|
||||
sudo dkp-pacman -Sy --noconfirm gba-dev
|
||||
# Copy from installed directory to cacheable directory
|
||||
sudo cp -r /opt/devkitpro "/home/runner"
|
||||
sudo chown -R $USER:$USER "/home/runner/devkitpro"
|
||||
- name: Save devkitPro to cache
|
||||
if: steps.cache-devkitpro.outputs.cache-hit != 'true'
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: /home/runner/devkitpro
|
||||
key: devkitpro-${{ runner.os }}-v1
|
||||
echo "Clearing Docker cache..."
|
||||
rm -rf /tmp/docker-cache
|
||||
echo "Docker cache cleared"
|
||||
|
||||
- name: Load or pull images
|
||||
run: |
|
||||
if [ -f /tmp/docker-cache/gbalatro.tar ]; then
|
||||
echo "Loading Image"
|
||||
docker load -i /tmp/docker-cache/gbalatro.tar
|
||||
else
|
||||
echo "Pulling Image"
|
||||
docker compose build gbalatro
|
||||
mkdir -p /tmp/docker-cache
|
||||
docker save $(docker compose config | grep image: | awk '{print $2}') -o /tmp/docker-cache/gbalatro.tar
|
||||
fi
|
||||
|
||||
- name: Export UID/GID for Compose
|
||||
run: |
|
||||
echo "UID=$(id -u)" >> "$GITHUB_ENV"
|
||||
echo "GID=$(id -g)" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Build the project
|
||||
run: |
|
||||
make
|
||||
run: docker compose -f docker-compose.yml run --rm gbalatro sh -c "make -j$(nproc) && ./scripts/get_memory_map.sh"
|
||||
|
||||
- name: 'Upload Artifact'
|
||||
if: ${{ inputs.upload-build-artifact }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-out
|
||||
path: build
|
||||
retention-days: 5
|
||||
|
|
|
|||
20
.github/workflows/clang_format_ci_workflow.yml
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
name: Run Clang Format Test
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
run-test:
|
||||
name: Verify code formatting
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install clang-format
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y clang-format
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Run clang-format
|
||||
run: clang-format --dry-run -Werror include/*.h source/*.c
|
||||
43
.github/workflows/nightly_release_ci_workflow.yml
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
name: Deploy Nightly
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 2 * * *' # run at 2 AM UTC
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-job:
|
||||
name: Run Build ROM Workflow
|
||||
uses: ./.github/workflows/build_ci_workflow.yml
|
||||
with:
|
||||
upload-build-artifact: true
|
||||
|
||||
nightly:
|
||||
name: Deploy nightly
|
||||
needs: build-job
|
||||
strategy:
|
||||
fail-fast: false
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download the build output
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
name: build-out
|
||||
path: build
|
||||
|
||||
- name: Package Release
|
||||
run: mv build/balatro-gba.gba build/balatro-gba_nightly_$(date +'%+4Y%m%d')_$(cat build/githash.txt).gba && zip -j release build/*.gba
|
||||
|
||||
- name: Deploy release
|
||||
uses: WebFreak001/deploy-nightly@v3.2.0
|
||||
with:
|
||||
upload_url: https://uploads.github.com/repos/GBALATRO/balatro-gba/releases/258698380/assets{?name,label}
|
||||
release_id: 258698380
|
||||
asset_path: ./release.zip # path to archive to upload
|
||||
asset_name: nightly-gbalatro-$$.zip # name to upload the release as, use $$ to insert date (YYYYMMDD) and 6 letter commit hash
|
||||
asset_content_type: application/zip
|
||||
max_releases: 7
|
||||
ignore_hash: false # Only upload unique versions
|
||||
30
.github/workflows/tagged_release_ci_workflow.yml
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
name: Release On Version Tag
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
jobs:
|
||||
build-job:
|
||||
name: Run Build ROM Workflow
|
||||
uses: ./.github/workflows/build_ci_workflow.yml
|
||||
with:
|
||||
upload-build-artifact: true
|
||||
|
||||
release:
|
||||
name: Release Tagged Version
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-job
|
||||
steps:
|
||||
- name: Download the build output
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
name: build-out
|
||||
path: build
|
||||
|
||||
- uses: softprops/action-gh-release@v2
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
with:
|
||||
files: |
|
||||
build/balatro-gba.gba
|
||||
21
.github/workflows/unit_tests_ci_workflow.yml
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
name: Run Unit Tests
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
run-unit-tests:
|
||||
name: Run Unit Tests
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Check and install GCC version
|
||||
run: ./ci_scripts/check_gcc_version.sh
|
||||
|
||||
- name: Run Unit Tests
|
||||
run: cd tests && ./run_tests.sh
|
||||
13
.gitignore
vendored
|
|
@ -1,6 +1,15 @@
|
|||
/.vscode
|
||||
/.idea
|
||||
/build
|
||||
/.vs
|
||||
_codeql_detected_source_root
|
||||
*.sln
|
||||
*.vcxproj*
|
||||
/balatro-gba/x64/**
|
||||
*.vcxproj*
|
||||
/balatro-gba/x64/**
|
||||
/tests/bitset/build
|
||||
/tests/list/build
|
||||
/tests/pool/build
|
||||
doc
|
||||
tests/util/.vscode
|
||||
tests/util/build
|
||||
audio/*.bak
|
||||
|
|
|
|||
100
CONTRIBUTING.md
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
# Developer Guide
|
||||
|
||||
Thank you for reading this. Below is a quick summary of expectations and tips to contribute.
|
||||
|
||||
## Scope
|
||||
We are limiting the scope of the project in order to not compete with the original Balatro and avoid a takedown by Playstack.
|
||||
We limited the scope to 52 jokers and reached that limit so currently there is no plan to add more jokers.
|
||||
See [the scope discussion](https://github.com/GBALATRO/balatro-gba/discussions/355).
|
||||
|
||||
## Art
|
||||
Before contributing art or if you need art for a code contribution check the [existing additional art thread](https://github.com/GBALATRO/balatro-gba/discussions/131) and the [joker art discussion](https://github.com/GBALATRO/balatro-gba/discussions/69) (though as said no more jokers are currently planned).
|
||||
|
||||
Note that there are color limitations for sprites and backgrounds resulting due to the GBA hardware. Sprites may not use more than 16 colors per sprite including transparency.
|
||||
Backgrounds may use more colors but notice that their palette is encoded in their PNGs and new colors need to be added manually to the palette. [See relevant PR](https://github.com/GBALATRO/balatro-gba/pull/350).
|
||||
|
||||
## CI Checks
|
||||
On pull-requests, various checks will be performed:
|
||||
1. **Formatting**: `clang-format` will be ran on every `.c/.h` file with [this configuration](https://github.com/GBALATRO/balatro-gba/blob/main/.clang-format). Failures will cause the CI to fail.
|
||||
2. **Unit Tests**: Unit tests are required to pass and are located in the repo [here](https://github.com/GBALATRO/balatro-gba/tree/main/tests).
|
||||
3. **Rom Build**: The ROM must successfully build with the `make -j$(nproc)` command.
|
||||
|
||||
## Code Style
|
||||
Besides the automatic formatting checks from `clang-format`, there is a looser set of code style rules. These are not strictly required, but is encouraged to be followed.
|
||||
The following details the code style rules including the enforced clang-format style.
|
||||
[Link in wiki](https://github.com/GBALATRO/balatro-gba/wiki/Code-Style-Guide)
|
||||
|
||||
## Building Documentation
|
||||
Doxygen is used to build documentation that can be opened in browser.
|
||||
|
||||
[Link in wiki](https://github.com/GBALATRO/balatro-gba/wiki/Documentation-for-Developers)
|
||||
|
||||
## Tools
|
||||
|
||||
### clang-format
|
||||
|
||||
Running `clang-format` locally is recommended before submitting a PR as it will fail the **CI Checks** if not properly formatted. It is recommended either:
|
||||
1. Run `clang-format` periodically and only commit formatted code.
|
||||
2. Run `clang-format` as a separate commit on larger changes, and review each modified hunk.
|
||||
|
||||
Either way, just ensure you manually review automatic changes.
|
||||
|
||||
#### VSCode
|
||||
|
||||
The recommended setup for VSCode is to install the [**clangd**](https://marketplace.visualstudio.com/items?itemName=llvm-vs-code-extensions.vscode-clangd) extension. It will provide helpful information in VSCode and can be used to format the code automatically according to the `.clang-format` file with **`Ctrl+Shift+I`**
|
||||
|
||||
There is an option to enable `"editor.formatOnSave"` in the VSCode `settings.json` file. You can also do this by opening the settings menu (`File->Preferences->Settings`) and searching `format on save`.
|
||||
|
||||
#### Manually
|
||||
|
||||
If installed locally and you'd prefer to use it in your shell. You can do the following
|
||||
|
||||
```sh
|
||||
# List warnings
|
||||
clang-format --dry-run -Werror include/*.h source/*.c
|
||||
# Modify all files inplace
|
||||
clang-format -i include/*.h source/*.c
|
||||
# Or just one
|
||||
clang-format -i include/blind.h
|
||||
```
|
||||
|
||||
#### Disabling Formatting
|
||||
|
||||
Sometimes `clang-format` rules need to be broken, like in the case of the [joker registry](https://github.com/GBALATRO/balatro-gba/blob/8fb0813cf5f7235b6450dc9a76252dda4d9b4a27/source/joker_effects.c#L333) and other tables or maps. If it makes sense, you can wrap code in `// clang-format off` and `// clang-format on`:
|
||||
|
||||
- **Without clang-format**:
|
||||
```c
|
||||
// clang-format off
|
||||
const table_of_tables =
|
||||
{
|
||||
{ TABLE_A, 1, tableAMap },
|
||||
{ TABLE_B, 2, tableBMap },
|
||||
{ TABLE_C, 3, tableCMap },
|
||||
{ TABLE_D, 4, tableDMap },
|
||||
{ TABLE_E, 5, tableEMap },
|
||||
{ TABLE_F, 6, tableFMap },
|
||||
{ TABLE_G, 7, tableGMap },
|
||||
}
|
||||
// clang-format on
|
||||
```
|
||||
- **With clang-format**:
|
||||
```c
|
||||
const table_of_tables =
|
||||
{
|
||||
{TABLE_A, 1, tableAMap}, {TABLE_B, 2, tableBMap}, {TABLE_C, 3, tableCMap},
|
||||
{TABLE_D, 4, tableDMap}, {TABLE_E, 5, tableEMap}, {TABLE_F, 6, tableFMap},
|
||||
{TABLE_G, 7, tableGMap},
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Scripts
|
||||
In the repo we use custom scripts located in the [`scripts`](https://github.com/GBALATRO/balatro-gba/tree/main/scripts) directory.
|
||||
|
||||
🟡 **Note**: `python3` and `bash` are required for these scripts.
|
||||
|
||||
- **get_hash.py**: Get git hash from ROM.
|
||||
- **generate_font.py**: Generate a font manually.
|
||||
- **get_memory_map.sh**: Print the memory map of the pre-allocated pools.
|
||||
|
||||
## Debugging
|
||||
It's recommended to use [mGBA](https://mgba.io/) for ROM testing and debugging. As it provides a [`gdbserver`](https://en.wikipedia.org/wiki/Gdbserver) via the `-g` flag `mgba -g build/balatro-gba.gba`. You can connect via `gdb` or here is a [great guide for vscode](https://felixjones.co.uk/mgba_gdb/vscode.html).
|
||||
6
Dockerfile.gbalatro
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
FROM devkitpro/devkitarm:20251117
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
python3-pil
|
||||
43
Makefile
|
|
@ -25,30 +25,37 @@ LIBTONC := $(DEVKITPRO)/libtonc
|
|||
# the makefile is found
|
||||
#
|
||||
#---------------------------------------------------------------------------------
|
||||
TARGET := $(notdir $(CURDIR))
|
||||
BUILD := build
|
||||
SOURCES := source
|
||||
INCLUDES := include
|
||||
DATA :=
|
||||
MUSIC := audio
|
||||
GRAPHICS := graphics
|
||||
TARGET := $(notdir $(CURDIR))
|
||||
BUILD := build
|
||||
SOURCES := source
|
||||
INCLUDES := include
|
||||
DATA :=
|
||||
MUSIC := audio
|
||||
GRAPHICS := graphics
|
||||
FONT := font
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# options for code generation
|
||||
#---------------------------------------------------------------------------------
|
||||
ARCH := -mthumb -mthumb-interwork
|
||||
|
||||
CFLAGS := -g -O3 -Wall -Werror\
|
||||
GIT_DIRTY := $(shell git diff-index --quiet HEAD -- || echo "-dirty")
|
||||
GIT_HASH := $(shell git rev-parse --short HEAD || echo "undef")
|
||||
GIT_C_FLAGS := -DGIT_HASH=\"$(GIT_HASH)\" -DGIT_DIRTY=\"$(GIT_DIRTY)\"
|
||||
|
||||
CFLAGS := -g -O3 -Wall -Werror -std=gnu23 \
|
||||
-mcpu=arm7tdmi -mtune=arm7tdmi \
|
||||
-ffast-math -fomit-frame-pointer -funroll-loops \
|
||||
$(ARCH)
|
||||
|
||||
CFLAGS += $(GIT_C_FLAGS)
|
||||
|
||||
CFLAGS += $(INCLUDE)
|
||||
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
|
||||
|
||||
ASFLAGS := -g $(ARCH)
|
||||
LDFLAGS = -g $(ARCH) -Wl,-Map,$(notdir $*.map)
|
||||
LDFLAGS = -g $(ARCH) -Wl,-Map,$(notdir $*.map),--undefined=balatro_version
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# any extra libraries we wish to link with the project
|
||||
|
|
@ -76,7 +83,8 @@ export OUTPUT := $(CURDIR)/$(BUILD)/$(TARGET)
|
|||
|
||||
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
|
||||
$(foreach dir,$(DATA),$(CURDIR)/$(dir)) \
|
||||
$(foreach dir,$(GRAPHICS),$(CURDIR)/$(dir))
|
||||
$(foreach dir,$(GRAPHICS),$(CURDIR)/$(dir)) \
|
||||
$(foreach dir,$(FONT),$(CURDIR)/$(dir)) \
|
||||
|
||||
export DEPSDIR := $(CURDIR)/$(BUILD)
|
||||
|
||||
|
|
@ -84,6 +92,7 @@ CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
|
|||
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
|
||||
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
|
||||
PNGFILES := $(foreach dir,$(GRAPHICS),$(notdir $(wildcard $(dir)/*.png)))
|
||||
FONTFILES := $(foreach dir,$(FONT),$(notdir $(wildcard $(dir)/*.png)))
|
||||
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
|
||||
|
||||
ifneq ($(strip $(MUSIC)),)
|
||||
|
|
@ -111,7 +120,9 @@ export OFILES_SOURCES := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
|
|||
|
||||
export OFILES_GRAPHICS := $(PNGFILES:.png=.o)
|
||||
|
||||
export OFILES := $(OFILES_BIN) $(OFILES_SOURCES) $(OFILES_GRAPHICS)
|
||||
export OFILES_FONT := $(FONTFILES:.png=.o)
|
||||
|
||||
export OFILES := $(OFILES_BIN) $(OFILES_SOURCES) $(OFILES_GRAPHICS) $(OFILES_FONT)
|
||||
|
||||
export HFILES := $(addsuffix .h,$(subst .,_,$(BINFILES))) $(PNGFILES:.png=.h)
|
||||
|
||||
|
|
@ -124,16 +135,22 @@ export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
|
|||
.PHONY: $(BUILD) clean
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
$(BUILD):
|
||||
$(BUILD): build/gbalatro_sys8.s
|
||||
@[ -d $@ ] || mkdir -p $@
|
||||
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
|
||||
@echo "$(GIT_HASH)$(GIT_DIRTY)" > $@/githash.txt
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
build/%.s: $(FONT)/%.png
|
||||
@echo Building font
|
||||
@mkdir -p $(BUILD)
|
||||
@python3 scripts/generate_font.py -i $< -o $@
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
clean:
|
||||
@echo clean ...
|
||||
@rm -fr $(BUILD) $(TARGET).elf $(TARGET).gba
|
||||
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
all: $(BUILD)
|
||||
|
||||
|
|
|
|||
123
README.md
|
|
@ -1,21 +1,20 @@
|
|||
# Balatro-GBA
|
||||
|
||||
[](https://github.com/cellos51/balatro-gba/actions)
|
||||
[](https://github.com/cellos51/balatro-gba/issues)
|
||||
[](https://github.com/cellos51/balatro-gba/pulls)
|
||||
[](https://github.com/cellos51/balatro-gba/actions)
|
||||
[](https://github.com/cellos51/balatro-gba/issues)
|
||||
[](https://github.com/cellos51/balatro-gba/pulls)
|
||||
[](https://github.com/cellos51/balatro-gba/discussions)
|
||||
|
||||
This is an attempt to recreate the game **'Balatro'** as accurately as possible, including all of the visual effects that make Balatro feel satisfying to play.
|
||||
This **tech-demo/proof of concept** is strictly limited in content to a minimal version of Balatro and will **NOT** recreate the full game. **This version is intended for people who already own and know how the official full game works.** Please refer to the Balatro Wiki if you need help understanding certain mechanics or abilities.
|
||||
### Disclaimer
|
||||
|
||||
This project is a non-profit fan demake of Balatro for the Game Boy Advance, meant to recreate it as accurately as possible including all the visuals that make it satisfying to play. It is **not affiliated with or endorsed by Playstack or LocalThunk** and **it is not to be sold**. This version is a **minimal tech-demo**, intended for those who already own and know the official game. Refer to the Balatro wiki below for descriptions of game mechanics and joker effects. All rights remain with the original holders.
|
||||
|
||||
<a href="https://balatrowiki.org/">
|
||||
<img src="https://custom-icon-badges.demolab.com/badge/Balatro%20Wiki-194c84?logo=bigjoker&logoColor=fff" alt="Balatro Wiki" width="155">
|
||||
</a>
|
||||
|
||||
### Disclaimer: This project is NOT endorsed by or affiliated with Playstack or LocalThunk
|
||||
#### This is a non-profit community fan project solely aimed to recreate a minimal version of Balatro on the Game Boy Advance as a tribute to the full Balatro and is not intended to infringe or draw sales away from the full game's release or any of the established works by Playstack and LocalThunk.
|
||||
#### All rights are reserved to their respective holders.
|
||||
|
||||
### Please buy the official full version from these sources below:
|
||||
|
||||
[](https://store.steampowered.com/app/2379780/Balatro/)
|
||||
[](https://play.google.com/store/apps/details?id=com.playstack.balatro.android)
|
||||
[](https://apps.apple.com/us/app/balatro/id6502453075)
|
||||
|
|
@ -24,21 +23,73 @@ This **tech-demo/proof of concept** is strictly limited in content to a minimal
|
|||
[](https://www.xbox.com/en-US/games/store/balatro/9PK087LNGJC5)
|
||||
[](https://www.humblebundle.com/store/balatro?srsltid=AfmBOoqS2De8T4kizzWxJS1pbvQosJ_bYCl4qvC6LA1YLPAh4sZ8vJqO)
|
||||
|
||||
<!-- The Gif is a little blurry but I think it looks fine -->
|
||||
<img src="example.gif" alt="Example GIF" width="800">
|
||||
---
|
||||
|
||||
<a href="https://github.com/cellos51/balatro-gba/releases/">
|
||||
<img src="https://img.shields.io/badge/Download_ROMs_from_the_Releases_tab-8A2BE2?&logo=github" alt="Download ROM" width="500">
|
||||
</a>
|
||||
|
||||
---
|
||||
|
||||
|
||||
https://github.com/user-attachments/assets/54a9e2e9-1a02-48d5-bb9d-5ab257a7e03b
|
||||
|
||||
|
||||
### Controls:
|
||||
(D-Pad: Navigation)
|
||||
|
||||
(A: Pick Card/Make Selections)
|
||||
|
||||
(B: Deselect All Cards)
|
||||
#### When on the hand row during round
|
||||
(L: Play Hand)
|
||||
|
||||
(R: Discard Hand)
|
||||
|
||||
(B: Deselect All Cards)
|
||||
|
||||
#### When on the joker row in the shop or during round
|
||||
(L: Sell Joker)
|
||||
|
||||
(R: Sort Suit/Rank)
|
||||
(Hold A: Swap Owned Jokers or Playing Cards in the Shop or Round)
|
||||
|
||||
# Contributing
|
||||
|
||||
If you would like to contribute, please read CONTRIBUTING.md.
|
||||
|
||||
(D-Pad: Navigation)
|
||||
# **Build Instructions:**
|
||||
|
||||
## **-Docker-**
|
||||
A docker compose file is provided to build this project. It provides a standard build environment for this projects CI/CD and can also be used to build the ROM locally.
|
||||
|
||||
_Some users may find this option to be the easiest way to build locally._
|
||||
- _This option **avoids** setting up the development environment as described below._
|
||||
- _No additional software besides **docker desktop** is required._
|
||||
|
||||
### Step-by-Step
|
||||
|
||||
1.) Install [docker desktop](https://docs.docker.com/desktop/) for your operating system.
|
||||
|
||||
2.) Open a terminal to this project's directory:
|
||||
- On **Linux** run `UID=$(id -u) GID=$(id -g) docker compose up`
|
||||
- On **Windows** run `docker compose up`
|
||||
|
||||
<details>
|
||||
<summary><i>How do I open a terminal in windows?</i></summary>
|
||||
|
||||
---
|
||||
|
||||
From the file explorer, you can open a folder in **powershell** (_a modern windows terminal_):
|
||||
|
||||
- **hold 'Shift'** and **Right Click** on the folder.
|
||||
|
||||
- Select **"Open PowerShell window here"** from the popup menu.
|
||||
|
||||
---
|
||||
|
||||
</details>
|
||||
|
||||
3.) Docker will build the project and the ROM will be in the same location as step 7 describes below.
|
||||
|
||||
## **-Windows-**
|
||||
Video Tutorial: https://youtu.be/72Zzo1VDYzQ?si=UDmEdbST1Cx1zZV2
|
||||
### With `Git` (not required)
|
||||
|
|
@ -57,6 +108,36 @@ Video Tutorial: https://youtu.be/72Zzo1VDYzQ?si=UDmEdbST1Cx1zZV2
|
|||
7.) After it completes, navigate through the `build` directory in the project folder and look for `balatro-gba.gba` and load it on an emulator or flashcart.
|
||||
### Without `Git`
|
||||
Disregard Steps 3-4 and instead click the green code button on the main repository page and press `Download Zip`. Unzip the folder and place it wherever you like. Then continue from Step 5.
|
||||
|
||||
## **-Linux-**
|
||||
|
||||
1.) Add the devkitPro repository using these instructions https://devkitpro.org/wiki/devkitPro_pacman
|
||||
|
||||
2.) Install devkitPro by running `sudo pacman -S gba-dev` and accepting all packages.
|
||||
|
||||
3.) Activate the devkitPro environment by running `source /etc/profile.d/devkit-env.sh` or opening a new shell.
|
||||
|
||||
4.) Follow instructions from the Windows tutorial starting from Step 3
|
||||
|
||||
## **-macOS-**
|
||||
1.) Install devkitPro installer using: https://github.com/devkitPro/installer and following https://devkitpro.org/wiki/devkitPro_pacman#macOS.
|
||||
> Note: You may have to install the installers directly from their url in a browser, as the installer script may not install correctly due to Cloudflare checks on their server. You can use one of the following urls:
|
||||
|
||||
> Apple Silicon: https://pkg.devkitpro.org/packages/macos-installers/devkitpro-pacman-installer.arm64.pkg
|
||||
|
||||
> Intel: https://pkg.devkitpro.org/packages/macos-installers/devkitpro-pacman-installer.x86_64.pkg
|
||||
|
||||
2.) Run `sudo dkp-pacman -S gba-dev`
|
||||
|
||||
3.) Verify that devkitPro is installed in '/opt/devkitpro'
|
||||
|
||||
4.) Add the following to your .bashrc or .zshrc (or export the variables in your shell session):
|
||||
- export DEVKITPRO=/opt/devkitpro
|
||||
- export DEVKITARM=$DEVKITPRO/devkitARM
|
||||
- export PATH=$PATH:$DEVKITPRO/tools/bin:$DEVKITPRO/pacman/bin
|
||||
|
||||
5.) Follow instructions from Windows tutorial step 4
|
||||
|
||||
## **Common Issues:**
|
||||
|
||||
#### 1. **When I run `make` it errors out and won't compile!**
|
||||
|
|
@ -70,3 +151,17 @@ Disregard Steps 3-4 and instead click the green code button on the main reposito
|
|||
|
||||
#### 4. **It says I don't have `Git` or `Make` installed!**
|
||||
- Use `pacman -S git` (not required) or `pacman -S make` although make should already be installed if you followed the instructions correctly.
|
||||
|
||||
# **Credits:**
|
||||
## **Game**
|
||||
This GBA implementation is based on Balatro which is designed and created by LocalThunk and published by Playstack.
|
||||
See repository contributors list for code contribution credits to this GBA implementation.
|
||||
## **Music**
|
||||
Music arrangement is made by @cellos51 and @MeirGavish based on original Balatro soundtrack by [LouisF](https://louisfmusic.com/) and [transcription by MrCrimit](https://musescore.com/user/8237321/scores/14590660).
|
||||
## **Imagery**
|
||||
Sprites and backgrounds are based on original Balatro imagery created by LocalThunk.
|
||||
See [Joker Art Discussion](https://github.com/cellos51/balatro-gba/discussions/69) for full credits for each joker sprite.
|
||||
## **Sounds**
|
||||
For the mult and xmult sound effects: [Toy records#06-E3-02.wav by poissonmort](https://freesound.org/s/253249/) used under License: Attribution 4.0
|
||||
|
||||
All other sound effects were created by LocalThunk and are used under Creative Commons - CC0 license.
|
||||
|
|
|
|||
BIN
audio/button.wav
Normal file
BIN
audio/chips_accum.wav
Normal file
BIN
audio/chips_card.wav
Normal file
BIN
audio/chips_generic.wav
Normal file
BIN
audio/mult.wav
Normal file
BIN
audio/pop.wav
Normal file
BIN
audio/xmult.wav
Normal file
17
ci_scripts/check_gcc_version.sh
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Check GCC version and install GCC 14 if needed
|
||||
check_gcc_version() {
|
||||
if command -v gcc &> /dev/null; then
|
||||
gcc_version=$(gcc -dumpversion | cut -d. -f1)
|
||||
if [ "$gcc_version" -lt 14 ]; then
|
||||
echo "GCC version $gcc_version detected. Installing GCC 14..."
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y gcc-14
|
||||
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 100
|
||||
echo "GCC 14 installed and set as default."
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
check_gcc_version
|
||||
11
docker-compose.yml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
services:
|
||||
gbalatro:
|
||||
image: gbalatro:dev
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.gbalatro
|
||||
working_dir: /balatro-gba
|
||||
volumes:
|
||||
- ./:/balatro-gba
|
||||
user: "${UID-1000}:${GID-1000}"
|
||||
command: ["sh", "-c", "make -j$(nproc)"]
|
||||
BIN
font/gbalatro_sys8.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 2.3 KiB |
BIN
graphics/big_blind_gfx.png
Normal file
|
After Width: | Height: | Size: 425 B |
|
Before Width: | Height: | Size: 597 B |
1
graphics/boss_blind_gfx.grit
Normal file
|
|
@ -0,0 +1 @@
|
|||
-gB4 -Mw4 -Mh4 -p!
|
||||
BIN
graphics/boss_blind_gfx.png
Normal file
|
After Width: | Height: | Size: 402 B |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.3 KiB |
1
graphics/joker_gfx15.grit
Normal file
|
|
@ -0,0 +1 @@
|
|||
-gB4 -Mw4 -Mh4 -m! -pn 16
|
||||
BIN
graphics/joker_gfx15.png
Normal file
|
After Width: | Height: | Size: 916 B |
1
graphics/joker_gfx16.grit
Normal file
|
|
@ -0,0 +1 @@
|
|||
-gB4 -Mw4 -Mh4 -m! -pn 16
|
||||
BIN
graphics/joker_gfx16.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
1
graphics/joker_gfx17.grit
Normal file
|
|
@ -0,0 +1 @@
|
|||
-gB4 -Mw4 -Mh4 -m! -pn 16
|
||||
BIN
graphics/joker_gfx17.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
1
graphics/joker_gfx18.grit
Normal file
|
|
@ -0,0 +1 @@
|
|||
-gB4 -Mw4 -Mh4 -m! -pn 16
|
||||
BIN
graphics/joker_gfx18.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
1
graphics/joker_gfx19.grit
Normal file
|
|
@ -0,0 +1 @@
|
|||
-gB4 -Mw4 -Mh4 -m! -pn 16
|
||||
BIN
graphics/joker_gfx19.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
1
graphics/joker_gfx20.grit
Normal file
|
|
@ -0,0 +1 @@
|
|||
-gB4 -Mw4 -Mh4 -m! -pn 16
|
||||
BIN
graphics/joker_gfx20.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
1
graphics/joker_gfx21.grit
Normal file
|
|
@ -0,0 +1 @@
|
|||
-gB4 -Mw4 -Mh4 -m! -pn 16
|
||||
BIN
graphics/joker_gfx21.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
1
graphics/joker_gfx22.grit
Normal file
|
|
@ -0,0 +1 @@
|
|||
-gB4 -Mw4 -Mh4 -m! -pn 16
|
||||
BIN
graphics/joker_gfx22.png
Normal file
|
After Width: | Height: | Size: 9.9 KiB |
1
graphics/joker_gfx23.grit
Normal file
|
|
@ -0,0 +1 @@
|
|||
-gB4 -Mw4 -Mh4 -m! -pn 16
|
||||
BIN
graphics/joker_gfx23.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
1
graphics/joker_gfx24.grit
Normal file
|
|
@ -0,0 +1 @@
|
|||
-gB4 -Mw4 -Mh4 -m! -pn 16
|
||||
BIN
graphics/joker_gfx24.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
1
graphics/joker_gfx25.grit
Normal file
|
|
@ -0,0 +1 @@
|
|||
-gB4 -Mw4 -Mh4 -m! -pn 16
|
||||
BIN
graphics/joker_gfx25.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 961 B After Width: | Height: | Size: 1016 B |
1
graphics/small_blind_gfx.grit
Normal file
|
|
@ -0,0 +1 @@
|
|||
-gB4 -Mw4 -Mh4 -p!
|
||||
BIN
graphics/small_blind_gfx.png
Normal file
|
After Width: | Height: | Size: 423 B |
|
|
@ -1,26 +1,93 @@
|
|||
/**
|
||||
* @file affine_background.h
|
||||
*
|
||||
* @brief Utilities for affine background on GBA
|
||||
*/
|
||||
#ifndef AFFINE_BACKGROUND_H
|
||||
#define AFFINE_BACKGROUND_H
|
||||
|
||||
#include <tonc.h>
|
||||
|
||||
#include "graphic_utils.h"
|
||||
|
||||
#define AFFINE_BG_IDX 2 // The index of the affine background BGCNT register etc.
|
||||
#define AFFINE_BG_PAL_LEN 16
|
||||
#define AFFINE_BG_PB (PAL_ROW_LEN * 10) // This isn't really a palette bank, just the starting index of the palette
|
||||
#include <tonc.h>
|
||||
|
||||
/**
|
||||
* @def AFFINE_BG_IDX
|
||||
* @brief The index of the affine background BGCNT register etc.
|
||||
*/
|
||||
#define AFFINE_BG_IDX 2
|
||||
|
||||
/**
|
||||
* @def AFFINE_BG_PAL_LEN
|
||||
* @brief Number of u16 colors available in the palette.
|
||||
*/
|
||||
#define AFFINE_BG_PAL_LEN 16
|
||||
|
||||
/**
|
||||
* @def AFFINE_BG_PB
|
||||
* @brief The starting index of the background palette.
|
||||
*/
|
||||
#define AFFINE_BG_PB (PAL_ROW_LEN * 10)
|
||||
|
||||
/**
|
||||
* @brief An ID to specify background rendering types.
|
||||
*/
|
||||
enum AffineBackgroundID
|
||||
{
|
||||
/**
|
||||
* @brief Display background for main menu.
|
||||
*
|
||||
* Signifies which background to use and provides a higher quality affine
|
||||
* mode to display when there is no game logic.
|
||||
*/
|
||||
AFFINE_BG_MAIN_MENU,
|
||||
/**
|
||||
* @brief Display background for game play.
|
||||
*
|
||||
* Signifies which background to use and provides a lower quality affine
|
||||
* mode to keep resources down during play.
|
||||
*/
|
||||
AFFINE_BG_GAME,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Initialize resources for affine background rendering
|
||||
*/
|
||||
void affine_background_init();
|
||||
|
||||
/**
|
||||
* @brief Interrupt routine to update display on HBLANK
|
||||
*/
|
||||
IWRAM_CODE void affine_background_hblank();
|
||||
|
||||
/**
|
||||
* @brief Per-frame update of the affine background
|
||||
*/
|
||||
IWRAM_CODE void affine_background_update();
|
||||
|
||||
/**
|
||||
* @brief Update the affine background color
|
||||
*
|
||||
* @param color @ref COLOR to set
|
||||
*/
|
||||
void affine_background_set_color(COLOR color);
|
||||
// Must be called with an array of size at least AFFINE_BG_PAL_LEN
|
||||
void affine_background_load_palette(const u16 *src);
|
||||
|
||||
/**
|
||||
* @brief Set the background palette
|
||||
*
|
||||
* Must be called with an array of size at least @ref AFFINE_BG_PAL_LEN
|
||||
*
|
||||
* @param src pointer to palette to set
|
||||
*/
|
||||
void affine_background_load_palette(const u16* src);
|
||||
|
||||
/**
|
||||
* @brief Update the background id
|
||||
*
|
||||
* Update the background id by configuring display registers and loading
|
||||
* the image and palette used for the specified id.
|
||||
*
|
||||
* @param new_bg @ref AffineBackgrounID to set
|
||||
*/
|
||||
void affine_background_change_background(enum AffineBackgroundID new_bg);
|
||||
|
||||
#endif // AFFINE_BACKGROUND_H
|
||||
|
|
|
|||
|
|
@ -1,16 +1,54 @@
|
|||
#ifndef AUDIO_UTILS_H
|
||||
#define AUDIO_UTILS_H
|
||||
|
||||
#include <mm_types.h>
|
||||
|
||||
#define MM_FULL_VOLUME 255
|
||||
#define MM_PAN_CENTER 128
|
||||
#define MM_BASE_PITCH_RATE 1024
|
||||
|
||||
#define SFX_DEFAULT_VOLUME MM_FULL_VOLUME
|
||||
#define SFX_DEFAULT_PAN MM_PAN_CENTER
|
||||
#define SFX_DEFAULT_HANDLE 0
|
||||
|
||||
void play_sfx(mm_word id, mm_word rate);
|
||||
|
||||
#endif
|
||||
/**
|
||||
* @file audio_utils.h
|
||||
*
|
||||
* @brief Utilities for using maxmod to play sound effects
|
||||
*/
|
||||
#ifndef AUDIO_UTILS_H
|
||||
#define AUDIO_UTILS_H
|
||||
|
||||
#include <mm_types.h>
|
||||
|
||||
/**
|
||||
* @def MM_FULL_VOLUME
|
||||
* @brief The maximum volume for maxmod mm_sound_effect.volume
|
||||
*/
|
||||
#define MM_FULL_VOLUME 255
|
||||
|
||||
/**
|
||||
* @def MM_PAN_CENTER
|
||||
* @brief The center pan of stereo audio for maxmod
|
||||
*/
|
||||
#define MM_PAN_CENTER 128
|
||||
|
||||
/**
|
||||
* @def MM_BASE_PITCH_RATE
|
||||
* @brief The default pitch rate for the sound effect played.
|
||||
*
|
||||
* Increasing the rate increases the pitch, while decreasing lowers the pitch.
|
||||
*/
|
||||
#define MM_BASE_PITCH_RATE 1024
|
||||
|
||||
/**
|
||||
* @def SFX_DEFAULT_VOLUME
|
||||
* @brief Default volume for sound effects
|
||||
*/
|
||||
#define SFX_DEFAULT_VOLUME MM_FULL_VOLUME
|
||||
|
||||
/**
|
||||
* @def SFX_DEFAULT_PAN
|
||||
* @brief The default stereo pan, will always be center pan.
|
||||
*/
|
||||
#define SFX_DEFAULT_PAN MM_PAN_CENTER
|
||||
|
||||
/**
|
||||
* @brief Play a sound effect, wrapper for mmEffectEx()
|
||||
* See https://maxmod.org/ref/functions/mm_sound_effect.html
|
||||
* and https://maxmod.org/ref/functions/mmEffectEx.html
|
||||
*
|
||||
* @param id the sound id to play, from maxmod compiled soundbank.h header
|
||||
* @param rate the pitch rate, the default value is @ref MM_BASE_PTCH_RATE
|
||||
* @param volume the volume ranging from 0 (silent) to 255 (loudest)
|
||||
*/
|
||||
void play_sfx(mm_word id, mm_word rate, mm_byte volume);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
207
include/bitset.h
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
/**
|
||||
* @file bitset.h
|
||||
*
|
||||
* @brief A bitset for operating on flags
|
||||
*/
|
||||
#ifndef BITSET_H
|
||||
#define BITSET_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* @def BITSET_BITS_PER_WORD
|
||||
* @brief Number of bits in a word for a bitset.
|
||||
*
|
||||
* Number of bits in a word for a bitset. Will always be 32 here.
|
||||
*/
|
||||
#define BITSET_BITS_PER_WORD 32
|
||||
|
||||
/**
|
||||
* @def BITSET_ARRAY_SIZE
|
||||
* @brief Number of words in a bitset
|
||||
*
|
||||
* Number of words in every bitset. This represents the maximum number and each
|
||||
* bitset will always use this number of words, though it's capacity can be any length
|
||||
* from `1` to `BITSET_BITS_PER_WORD * BITSET_ARRAY_SIZE`
|
||||
*/
|
||||
#define BITSET_ARRAY_SIZE 8
|
||||
|
||||
/**
|
||||
* @def BITSET_MAX_BITS
|
||||
* @brief Maximum number of bits in a bitset
|
||||
*/
|
||||
#define BITSET_MAX_BITS (BITSET_BITS_PER_WORD * BITSET_ARRAY_SIZE)
|
||||
|
||||
/**
|
||||
* @brief A bitset spread across multiple `uint32_t` words
|
||||
*/
|
||||
typedef struct Bitset
|
||||
{
|
||||
/**
|
||||
* @brief Word array of `uint32_t` to hold the bitset data
|
||||
*/
|
||||
uint32_t* w;
|
||||
|
||||
/**
|
||||
* @brief Number of bits in a word, will be 32
|
||||
*/
|
||||
uint32_t nbits;
|
||||
|
||||
/**
|
||||
* @brief Number of words int the `w` array
|
||||
*/
|
||||
uint32_t nwords;
|
||||
|
||||
/**
|
||||
* @brief Number of actual flags (nbits * nwords)
|
||||
*/
|
||||
uint32_t cap;
|
||||
} Bitset;
|
||||
|
||||
/**
|
||||
* @brief An iterator into a @ref Bitset
|
||||
*
|
||||
* This iterator will parse and find the next index to a '1' bit as efficiently as possible.
|
||||
*
|
||||
* There is no implementation of the following (yet):
|
||||
* - Reverse iteration
|
||||
* - Bit-by-bit iteration
|
||||
* - Iterating on offsets to '0' bits
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
/**
|
||||
* @brief @ref Bitset this is iterating through
|
||||
*/
|
||||
const Bitset* bitset;
|
||||
|
||||
/**
|
||||
* @brief Current word the iterator is on
|
||||
*/
|
||||
int word;
|
||||
|
||||
/**
|
||||
* @brief Current bit the iterator is on
|
||||
*/
|
||||
int bit;
|
||||
|
||||
/**
|
||||
* @brief Number of bits that have been iterated through in total
|
||||
*/
|
||||
int itr;
|
||||
} BitsetItr;
|
||||
|
||||
/**
|
||||
* @brief Set a flag in a bitset to a value
|
||||
*
|
||||
* @param bitset A @ref Bitset to operate on
|
||||
* @param idx the index of the flag to set
|
||||
* @param on the value to set the flag to
|
||||
*/
|
||||
void bitset_set_idx(Bitset* bitset, int idx, bool on);
|
||||
|
||||
/**
|
||||
* @brief Get the value of a flag
|
||||
*
|
||||
* @param bitset A @ref Bitset to operate on
|
||||
* @param idx the index of the flag to get
|
||||
*
|
||||
* @return the value of the flag as `true` or `false`
|
||||
*/
|
||||
bool bitset_get_idx(Bitset* bitset, int idx);
|
||||
|
||||
/**
|
||||
* @brief Set the next free index in the bitset and return the index value
|
||||
*
|
||||
* @param bitset A @ref Bitset to operate on
|
||||
*
|
||||
* @return The index of the bit that was set
|
||||
*/
|
||||
int bitset_set_next_free_idx(Bitset* bitset);
|
||||
|
||||
/**
|
||||
* @brief Clear the bitset, all to 0
|
||||
*
|
||||
* @param bitset A @ref Bitset to operate on
|
||||
*/
|
||||
void bitset_clear(Bitset* bitset);
|
||||
|
||||
/**
|
||||
* @brief Check if a bitset is empty (all 0's)
|
||||
*
|
||||
* @param bitset A @ref Bitset to operate on
|
||||
*
|
||||
* @return `true` if empty, `false` otherwise
|
||||
*/
|
||||
bool bitset_is_empty(Bitset* bitset);
|
||||
|
||||
/**
|
||||
* @brief Count how many bits are set to `1` in a bitset
|
||||
*
|
||||
* @param bitset A @ref Bitset to operate on
|
||||
*
|
||||
* @return The number of flags set to `1` in a bitset
|
||||
*/
|
||||
int bitset_num_set_bits(Bitset* bitset);
|
||||
|
||||
/**
|
||||
* @brief Find the index of the nth set bit
|
||||
*
|
||||
* Find the index of the nth flag set to `1`. This function is useful to get one value quickly,
|
||||
* but does not operate iteratively well. Use a @BitsetItr for iterative access to a bitset.
|
||||
*
|
||||
* @param bitset A @ref Bitset to operate on
|
||||
*
|
||||
* @return The index of the nth flag set to `1` in the bitset
|
||||
*/
|
||||
int bitset_find_idx_of_nth_set(const Bitset* bitset, int n);
|
||||
|
||||
/**
|
||||
* @brief Declare a @ref BitsetItr
|
||||
*
|
||||
* @param bitset A @ref Bitset to operate on
|
||||
*
|
||||
* @return A newly constructed BitsetItr
|
||||
*/
|
||||
BitsetItr bitset_itr_create(const Bitset* bitset);
|
||||
|
||||
/**
|
||||
* @brief Get the index of the next set bit in the bitset from a @ref BitsetItr
|
||||
*
|
||||
* @param itr A @ref BitsetItr to operate on
|
||||
*
|
||||
* @return a positive number if successful, UNDEFINED otherwise (out-of-bounds)
|
||||
*/
|
||||
int bitset_itr_next(BitsetItr* itr);
|
||||
|
||||
/**
|
||||
* @def BITSET_DEFINE
|
||||
* @brief Make a standard bitset
|
||||
*
|
||||
* Make a bitset with a valid static array to store it's array of words.
|
||||
*
|
||||
* Use this to define bitsets in the code, specifically as a `static` scoped
|
||||
* variable. The passed `name` will be the same name as the bitset.
|
||||
*
|
||||
* Usage example:
|
||||
*
|
||||
* ```c
|
||||
* BITSET_DEFINE(_my_bitset, 128);
|
||||
* // normal operation...
|
||||
* bitset_clear(&_my_bitset);
|
||||
* ```
|
||||
*
|
||||
* @param name the name of the bitset
|
||||
* @param capacity the capacity of the bitset
|
||||
*/
|
||||
#define BITSET_DEFINE(name, capacity) \
|
||||
static uint32_t name##_w[BITSET_ARRAY_SIZE] = {0}; \
|
||||
static Bitset name = { \
|
||||
.w = name##_w, \
|
||||
.nbits = BITSET_BITS_PER_WORD, \
|
||||
.nwords = BITSET_ARRAY_SIZE, \
|
||||
.cap = capacity, \
|
||||
};
|
||||
|
||||
#endif // BITSET_H
|
||||
|
|
@ -2,49 +2,81 @@
|
|||
#define BLIND_H
|
||||
|
||||
#include "sprite.h"
|
||||
|
||||
#define MAX_ANTE 8 // The GBA's max uint value is around 4 billion, so we're going to not add endless mode for simplicity's sake
|
||||
|
||||
// +1 is added because we'll actually be indexing at 1, but if something causes you to go to ante 0, there will still be a value there.
|
||||
static const int ante_lut[MAX_ANTE + 1] = {100, 300, 800, 2000, 5000, 11000, 20000, 35000, 50000};
|
||||
|
||||
#define MAX_BLINDS 3
|
||||
|
||||
#define SMALL_BLIND 0
|
||||
#define BIG_BLIND 1
|
||||
#define BOSS_BLIND 2
|
||||
// The GBA's max uint value is around 4 billion, so we're going to not add endless mode for
|
||||
// simplicity's sake
|
||||
#define MAX_ANTE 8
|
||||
|
||||
#define SMALL_BLIND_PB 1
|
||||
#define BIG_BLIND_PB 2
|
||||
#define BOSS_BLIND_PB 3
|
||||
#define BIG_BLIND_PB 2
|
||||
#define BOSS_BLIND_PB 3
|
||||
|
||||
#define BLIND_SPRITE_OFFSET 16
|
||||
#define SMALL_BLIND_TID 960
|
||||
#define BIG_BLIND_TID (BLIND_SPRITE_OFFSET + SMALL_BLIND_TID)
|
||||
#define BOSS_BLIND_TID (BLIND_SPRITE_OFFSET + BIG_BLIND_TID)
|
||||
#define BLIND_SPRITE_OFFSET 16
|
||||
#define BLIND_SPRITE_COPY_SIZE BLIND_SPRITE_OFFSET * 8 // 8 ints per tile
|
||||
#define SMALL_BLIND_TID 960
|
||||
#define BIG_BLIND_TID (BLIND_SPRITE_OFFSET + SMALL_BLIND_TID)
|
||||
#define BOSS_BLIND_TID (BLIND_SPRITE_OFFSET + BIG_BLIND_TID)
|
||||
|
||||
#define BLIND_TEXT_COLOR_INDEX 1
|
||||
#define BLIND_SHADOW_COLOR_INDEX 2
|
||||
#define BLIND_HIGHLIGHT_COLOR_INDEX 3
|
||||
#define BLIND_MAIN_COLOR_INDEX 4
|
||||
#define BLIND_BACKGROUND_MAIN_COLOR_INDEX 5
|
||||
#define BLIND_BACKGROUND_SECONDARY_COLOR_INDEX 6
|
||||
#define BLIND_BACKGROUND_SHADOW_COLOR_INDEX 7
|
||||
enum BlindColorIndex
|
||||
{
|
||||
BLIND_TEXT_COLOR_INDEX = 1,
|
||||
BLIND_SHADOW_COLOR_INDEX = 2,
|
||||
BLIND_HIGHLIGHT_COLOR_INDEX = 3,
|
||||
BLIND_MAIN_COLOR_INDEX = 4,
|
||||
BLIND_BACKGROUND_MAIN_COLOR_INDEX = 5,
|
||||
BLIND_BACKGROUND_SECONDARY_COLOR_INDEX = 6,
|
||||
BLIND_BACKGROUND_SHADOW_COLOR_INDEX = 7,
|
||||
};
|
||||
|
||||
#define BLIND_TYPE_INFO_TABLE \
|
||||
BLIND_INFO(SMALL, small, FIX_ONE, 3) \
|
||||
BLIND_INFO(BIG, big, (FIX_ONE * 3) / 2, 4) \
|
||||
BLIND_INFO(BOSS, boss, FIX_ONE * 2, 5)
|
||||
|
||||
// clang-format off
|
||||
enum BlindType
|
||||
{
|
||||
#define BLIND_INFO(NAME, name, multi, reward) \
|
||||
BLIND_TYPE_##NAME,
|
||||
BLIND_TYPE_INFO_TABLE
|
||||
#undef BLIND_INFO
|
||||
BLIND_TYPE_MAX,
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
enum BlindState
|
||||
{
|
||||
BLIND_CURRENT,
|
||||
BLIND_UPCOMING,
|
||||
BLIND_DEFEATED,
|
||||
BLIND_SKIPPED
|
||||
BLIND_STATE_CURRENT,
|
||||
BLIND_STATE_UPCOMING,
|
||||
BLIND_STATE_DEFEATED,
|
||||
BLIND_STATE_SKIPPED,
|
||||
BLIND_STATE_MAX,
|
||||
};
|
||||
|
||||
// TODO: Move this to a common interface for other palettes
|
||||
typedef struct
|
||||
{
|
||||
const unsigned int* tiles;
|
||||
const u16* palette;
|
||||
u32 tid;
|
||||
u32 pb;
|
||||
} BlindGfxInfo;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
enum BlindType type;
|
||||
BlindGfxInfo gfx_info;
|
||||
FIXED score_req_multipler;
|
||||
s32 reward;
|
||||
} Blind;
|
||||
|
||||
void blind_init();
|
||||
|
||||
int blind_get_requirement(int type, int ante);
|
||||
int blind_get_reward(int type);
|
||||
u16 blind_get_color(int type, int index);
|
||||
void blind_set_boss_graphics(const unsigned int* tiles, const u16* palette);
|
||||
|
||||
Sprite *blind_token_new(int type, int x, int y, int sprite_index);
|
||||
u32 blind_get_requirement(enum BlindType type, int ante);
|
||||
int blind_get_reward(enum BlindType type);
|
||||
u16 blind_get_color(enum BlindType type, enum BlindColorIndex index);
|
||||
|
||||
#endif // BLIND_H
|
||||
Sprite* blind_token_new(enum BlindType type, int x, int y, int sprite_index);
|
||||
|
||||
#endif // BLIND_H
|
||||
|
|
|
|||
76
include/button.h
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
/**
|
||||
* @file button.h
|
||||
*
|
||||
* @brief A button structure containing the common button functionalities
|
||||
*/
|
||||
|
||||
#ifndef BUTTON_H
|
||||
#define BUTTON_H
|
||||
|
||||
#include <tonc_types.h>
|
||||
|
||||
#define BTN_HIGHLIGHT_COLOR 0xFFFF
|
||||
|
||||
#define BUTTON_SFX_VOLUME 154 // 60% of MM_FULL_VOLUME
|
||||
|
||||
/**
|
||||
* @brief The function to be called when the button is pressed
|
||||
*/
|
||||
typedef void (*ButtonOnPressedFunc)(void);
|
||||
|
||||
/**
|
||||
* @brief Returns true if the button should be activated when pressed.
|
||||
*/
|
||||
typedef bool (*ButtonCanBePressedFunc)(void);
|
||||
|
||||
/**
|
||||
* @brief A button representation
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
// Using u8 for the palette indices to make sure they don't overflow pal_bg_mem
|
||||
|
||||
/**
|
||||
* @brief Palette index of the button border whose color is updated depending on highlight.
|
||||
*/
|
||||
u8 border_pal_idx;
|
||||
|
||||
/**
|
||||
* @brief Palette index of the color of the button itself, used for the border when not
|
||||
* highlighted.
|
||||
*/
|
||||
u8 button_pal_idx;
|
||||
|
||||
/**
|
||||
* @brief Called when the button is pressed if @ref can_be_pressed, don't call this directly,
|
||||
* call @ref button_press() instead.
|
||||
* Should not be set to NULL.
|
||||
*/
|
||||
ButtonOnPressedFunc on_pressed;
|
||||
|
||||
/**
|
||||
* @brief Returns true if the button should be activated when pressed.
|
||||
* Can be NULL if button can always be pressed.
|
||||
*/
|
||||
ButtonCanBePressedFunc can_be_pressed;
|
||||
} Button;
|
||||
|
||||
/**
|
||||
* @brief Set the button highlight on or off.
|
||||
* @param button The button to modify. No-op on NULL.
|
||||
* @param highlight true to highlight, false to unhighlight.
|
||||
*/
|
||||
void button_set_highlight(Button* button, bool highlight);
|
||||
|
||||
/**
|
||||
* @brief Execute a button press, by calling the button's @ref on_pressed function.
|
||||
* This checks the button's @ref can_be_pressed function and will do nothing if
|
||||
* returns false.
|
||||
*
|
||||
* Button presses should go through this function rather than directly call @ref on_pressed.
|
||||
*
|
||||
* @param button The button being pressed. No-op on NULL.
|
||||
*/
|
||||
void button_press(Button* button);
|
||||
|
||||
#endif // BUTTON_H
|
||||
|
|
@ -1,44 +1,47 @@
|
|||
#ifndef CARD_H
|
||||
#define CARD_H
|
||||
|
||||
#include <tonc.h>
|
||||
#include <maxmod.h>
|
||||
|
||||
#include "sprite.h"
|
||||
|
||||
#define CARD_TID 0
|
||||
#define CARD_SPRITE_OFFSET 16
|
||||
#define CARD_PB 0
|
||||
#include <maxmod.h>
|
||||
#include <tonc.h>
|
||||
|
||||
#define MAX_CARDS (NUM_SUITS * NUM_RANKS)
|
||||
#define MAX_CARDS_ON_SCREEN 16
|
||||
|
||||
#define CARD_TID 0
|
||||
#define CARD_SPRITE_OFFSET 16
|
||||
#define CARD_PB 0
|
||||
#define CARD_STARTING_LAYER 0
|
||||
|
||||
// Card suits
|
||||
#define HEARTS 0
|
||||
#define CLUBS 1
|
||||
#define DIAMONDS 2
|
||||
#define SPADES 3
|
||||
#define DIAMONDS 0
|
||||
#define CLUBS 1
|
||||
#define HEARTS 2
|
||||
#define SPADES 3
|
||||
#define NUM_SUITS 4
|
||||
|
||||
// Card ranks
|
||||
#define TWO 0
|
||||
#define THREE 1
|
||||
#define FOUR 2
|
||||
#define FIVE 3
|
||||
#define SIX 4
|
||||
#define SEVEN 5
|
||||
#define EIGHT 6
|
||||
#define NINE 7
|
||||
#define TEN 8
|
||||
#define JACK 9
|
||||
#define QUEEN 10
|
||||
#define KING 11
|
||||
#define ACE 12
|
||||
#define NUM_RANKS 13
|
||||
#define TWO 0
|
||||
#define THREE 1
|
||||
#define FOUR 2
|
||||
#define FIVE 3
|
||||
#define SIX 4
|
||||
#define SEVEN 5
|
||||
#define EIGHT 6
|
||||
#define NINE 7
|
||||
#define TEN 8
|
||||
#define JACK 9
|
||||
#define QUEEN 10
|
||||
#define KING 11
|
||||
#define ACE 12
|
||||
#define NUM_RANKS 13
|
||||
#define RANK_OFFSET 2 // Because the first rank is 2 and ranks start at 0
|
||||
|
||||
#define IMPOSSIBLY_HIGH_CARD_VALUE 100
|
||||
|
||||
// Card types
|
||||
typedef struct
|
||||
typedef struct Card
|
||||
{
|
||||
u8 suit;
|
||||
u8 rank;
|
||||
|
|
@ -46,27 +49,28 @@ typedef struct
|
|||
|
||||
typedef struct CardObject
|
||||
{
|
||||
Card *card;
|
||||
SpriteObject *sprite_object;
|
||||
Card* card;
|
||||
SpriteObject* sprite_object;
|
||||
bool selected;
|
||||
} CardObject;
|
||||
|
||||
// Card functions
|
||||
void card_init();
|
||||
|
||||
// Card methods
|
||||
Card *card_new(u8 suit, u8 rank);
|
||||
void card_destroy(Card **card);
|
||||
u8 card_get_value(Card *card);
|
||||
Card* card_new(u8 suit, u8 rank);
|
||||
void card_destroy(Card** card);
|
||||
u8 card_get_value(Card* card);
|
||||
|
||||
// CardObject methods
|
||||
CardObject *card_object_new(Card *card);
|
||||
void card_object_destroy(CardObject **card_object);
|
||||
void card_object_update(CardObject *card_object); // Update the card object position and scale
|
||||
void card_object_set_sprite(CardObject *card_object, int layer);
|
||||
CardObject* card_object_new(Card* card);
|
||||
void card_object_destroy(CardObject** card_object);
|
||||
void card_object_update(CardObject* card_object); // Update the card object position and scale
|
||||
void card_object_set_sprite(CardObject* card_object, int layer);
|
||||
void card_object_shake(CardObject* card_object, mm_word sound_id);
|
||||
|
||||
void card_object_set_selected(CardObject* card_object, bool selected);
|
||||
bool card_object_is_selected(CardObject* card_object);
|
||||
Sprite* card_object_get_sprite(CardObject* card_object);
|
||||
|
||||
#endif // CARD_H
|
||||
#endif // CARD_H
|
||||
|
|
|
|||
12
include/def_balatro_mempool.h
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#include "card.h"
|
||||
#include "joker.h"
|
||||
#include "list.h"
|
||||
#include "sprite.h"
|
||||
|
||||
POOL_ENTRY(Sprite, MAX_SPRITES);
|
||||
POOL_ENTRY(SpriteObject, MAX_SPRITE_OBJECTS);
|
||||
POOL_ENTRY(Joker, MAX_ACTIVE_JOKERS);
|
||||
POOL_ENTRY(JokerObject, MAX_ACTIVE_JOKERS);
|
||||
POOL_ENTRY(Card, MAX_CARDS);
|
||||
POOL_ENTRY(CardObject, MAX_CARDS_ON_SCREEN);
|
||||
POOL_ENTRY(ListNode, MAX_LIST_NODES);
|
||||
26
include/def_joker_gfx_table.h
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
DEF_JOKER_GFX(0)
|
||||
DEF_JOKER_GFX(1)
|
||||
DEF_JOKER_GFX(2)
|
||||
DEF_JOKER_GFX(3)
|
||||
DEF_JOKER_GFX(4)
|
||||
DEF_JOKER_GFX(5)
|
||||
DEF_JOKER_GFX(6)
|
||||
DEF_JOKER_GFX(7)
|
||||
DEF_JOKER_GFX(8)
|
||||
DEF_JOKER_GFX(9)
|
||||
DEF_JOKER_GFX(10)
|
||||
DEF_JOKER_GFX(11)
|
||||
DEF_JOKER_GFX(12)
|
||||
DEF_JOKER_GFX(13)
|
||||
DEF_JOKER_GFX(14)
|
||||
DEF_JOKER_GFX(15)
|
||||
DEF_JOKER_GFX(16)
|
||||
DEF_JOKER_GFX(17)
|
||||
DEF_JOKER_GFX(18)
|
||||
DEF_JOKER_GFX(19)
|
||||
DEF_JOKER_GFX(20)
|
||||
DEF_JOKER_GFX(21)
|
||||
DEF_JOKER_GFX(22)
|
||||
DEF_JOKER_GFX(23)
|
||||
DEF_JOKER_GFX(24)
|
||||
DEF_JOKER_GFX(25)
|
||||
11
include/def_state_info_table.h
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
// clang-format off
|
||||
// (stateEnum, on_init, on_update, on_exit)
|
||||
DEF_STATE_INFO(GAME_STATE_SPLASH_SCREEN, splash_screen_on_init, splash_screen_on_update, splash_screen_on_exit)
|
||||
DEF_STATE_INFO(GAME_STATE_MAIN_MENU, game_main_menu_on_init, game_main_menu_on_update, noop)
|
||||
DEF_STATE_INFO(GAME_STATE_PLAYING, game_round_on_init, game_playing_on_update, noop)
|
||||
DEF_STATE_INFO(GAME_STATE_ROUND_END, noop, game_round_end_on_update, game_round_end_on_exit)
|
||||
DEF_STATE_INFO(GAME_STATE_SHOP, noop, game_shop_on_update, game_shop_on_exit)
|
||||
DEF_STATE_INFO(GAME_STATE_BLIND_SELECT, game_blind_select_on_init, game_blind_select_on_update, game_blind_select_on_exit)
|
||||
DEF_STATE_INFO(GAME_STATE_LOSE, game_lose_on_init, game_lose_on_update, game_over_on_exit)
|
||||
DEF_STATE_INFO(GAME_STATE_WIN, game_win_on_init, game_win_on_update, game_over_on_exit)
|
||||
// clang-format on
|
||||
73
include/font.h
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
/**
|
||||
* @file font.h
|
||||
*
|
||||
* @brief Utility file to interact with custom font
|
||||
*/
|
||||
#ifndef FONT_H
|
||||
#define FONT_H
|
||||
|
||||
/** @name Decimal Point Fonts
|
||||
* @brief A set of macros to map the fonts decimal-point values (e.g. ".1")
|
||||
* to their replaced characters.
|
||||
*
|
||||
* "FP#" -> Font "Point" "number"
|
||||
*
|
||||
*
|
||||
* For example:
|
||||
* ```c
|
||||
* tte_printf("Testing " FP0_STR " Something!");
|
||||
* ```
|
||||
* prints "Testing .0 Something!"
|
||||
*
|
||||
* The map is the following:
|
||||
*
|
||||
* ```c
|
||||
* '&' == '.0'
|
||||
* '^' == '.1'
|
||||
* '}' == '.2'
|
||||
* '{' == '.3'
|
||||
* '|' == '.4'
|
||||
* '`' == '.5'
|
||||
* '<' == '.6'
|
||||
* '>' == '.7'
|
||||
* '_' == '.8'
|
||||
* ';' == '.9'
|
||||
* ```
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
#define FP0_STR "&" // .0
|
||||
#define FP1_STR "^" // .1
|
||||
#define FP2_STR "}" // .2
|
||||
#define FP3_STR "{" // .3
|
||||
#define FP4_STR "|" // .4
|
||||
#define FP5_STR "`" // .5
|
||||
#define FP6_STR "<" // .6
|
||||
#define FP7_STR ">" // .7
|
||||
#define FP8_STR "_" // .8
|
||||
#define FP9_STR ";" // .9
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @brief Get the decimal point string equivalent of 0-9 from integer param.
|
||||
*
|
||||
* @param val Value between 0-9 to get ".0" to ".9" equivalents. Note that
|
||||
* if a value outside of range is passed it will perform '% 10'
|
||||
* on that value and the last decimal digit will be used.
|
||||
*
|
||||
* @return A char* to the string values of '.0' to '.9'
|
||||
*/
|
||||
const char* get_font_point_str(int val);
|
||||
|
||||
/**
|
||||
* @brief Get the decimal point char equivalent of 0-9 from char param.
|
||||
*
|
||||
* @param digit_char A char of a digit between '0'-'9' to get '.0' to '.9' equivalents. Note that
|
||||
* if a char outside of range is passed it the return value will be within limits
|
||||
* but is not defined with any relationship to the input.
|
||||
*
|
||||
* @return A char representing a value in the font within the range of '.0' to '.9'
|
||||
*/
|
||||
char digit_char_to_font_point(char digit_char);
|
||||
|
||||
#endif // FONT_H
|
||||
170
include/game.h
|
|
@ -1,48 +1,69 @@
|
|||
#ifndef GAME_H
|
||||
#define GAME_H
|
||||
|
||||
#define MAX_HAND_SIZE 16
|
||||
#define MAX_DECK_SIZE 52
|
||||
#define MAX_JOKERS_HELD_SIZE 5 // This doesn't account for negatives right now.
|
||||
#define MAX_SHOP_JOKERS 2 // TODO: Make this dynamic and allow for other items besides jokers
|
||||
#define MAX_SELECTION_SIZE 5
|
||||
#define MAX_CARD_SCORE_DIGITS 2 // Current digit limit for score received from cards including mult etc. from jokers
|
||||
#define MAX_CARD_SCORE_STR_LEN (MAX_CARD_SCORE_DIGITS + 1) // For the '+' or 'X'
|
||||
#define FRAMES(x) (((x) + game_speed - 1) / game_speed)
|
||||
#include <tonc.h>
|
||||
|
||||
// TODO: Turn into enum?
|
||||
#define BG_ID_CARD_SELECTING 1
|
||||
#define BG_ID_CARD_PLAYING 2
|
||||
#define BG_ID_ROUND_END 3
|
||||
#define BG_ID_SHOP 4
|
||||
#define BG_ID_BLIND_SELECT 5
|
||||
#define BG_ID_MAIN_MENU 6
|
||||
#define MAX_HAND_SIZE 16
|
||||
#define MAX_DECK_SIZE 52
|
||||
#define MAX_JOKERS_HELD_SIZE 5 // This doesn't account for negatives right now.
|
||||
#define MAX_SHOP_JOKERS 2 // TODO: Make this dynamic and allow for other items besides jokers
|
||||
#define MAX_SELECTION_SIZE 5
|
||||
#define FRAMES(x) (((x) + game_speed - 1) / game_speed)
|
||||
|
||||
// TODO: Can make these dynamic to support interest-related jokers and vouchers
|
||||
#define MAX_INTEREST 5
|
||||
#define INTEREST_PER_5 1
|
||||
|
||||
// Input bindings
|
||||
#define SELECT_CARD KEY_A
|
||||
#define SELECT_CARD KEY_A
|
||||
#define DESELECT_CARDS KEY_B
|
||||
#define PEEK_DECK KEY_L // Not implemented
|
||||
#define SORT_HAND KEY_R
|
||||
#define PAUSE_GAME KEY_START // Not implemented
|
||||
#define SELL_KEY KEY_L
|
||||
#define PEEK_DECK KEY_L // Not implemented
|
||||
#define SORT_HAND KEY_R
|
||||
#define PAUSE_GAME KEY_START // Not implemented
|
||||
#define SELL_KEY KEY_L
|
||||
|
||||
// Matching the position of the on-screen buttons
|
||||
#define PLAY_HAND_KEY KEY_L
|
||||
// Same value as SELL_KEY - activated on the joker row, while this is activated on the hand row
|
||||
|
||||
#define DISCARD_HAND_KEY KEY_R
|
||||
|
||||
struct List;
|
||||
typedef struct List List;
|
||||
|
||||
// Utility functions for other files
|
||||
typedef struct CardObject CardObject;
|
||||
typedef struct Card Card;
|
||||
typedef struct JokerObject JokerObject;
|
||||
|
||||
enum BackgroundId
|
||||
{
|
||||
BG_NONE,
|
||||
BG_CARD_SELECTING,
|
||||
BG_CARD_PLAYING,
|
||||
BG_ROUND_END,
|
||||
BG_SHOP,
|
||||
BG_BLIND_SELECT,
|
||||
BG_MAIN_MENU
|
||||
};
|
||||
|
||||
// Enum value names in ../include/def_state_info_table.h
|
||||
enum GameState
|
||||
{
|
||||
GAME_SPLASH_SCREEN,
|
||||
GAME_MAIN_MENU,
|
||||
GAME_PLAYING,
|
||||
GAME_ROUND_END,
|
||||
GAME_SHOP,
|
||||
GAME_BLIND_SELECT,
|
||||
GAME_LOSE,
|
||||
GAME_WIN
|
||||
#define DEF_STATE_INFO(stateEnum, on_init, on_update, on_exit) stateEnum,
|
||||
#include "def_state_info_table.h"
|
||||
#undef DEF_STATE_INFO
|
||||
GAME_STATE_MAX,
|
||||
GAME_STATE_UNDEFINED
|
||||
};
|
||||
|
||||
enum HandState
|
||||
{
|
||||
HAND_DRAW,
|
||||
HAND_SELECT,
|
||||
HAND_SHUFFLING, // This is actually a misnomer because it's used for the deck, but it mechanically makes sense to be a state of the hand
|
||||
// This is actually a misnomer because it's used for the deck
|
||||
// but it mechanically makes sense to be a state of the hand
|
||||
HAND_SHUFFLING,
|
||||
HAND_DISCARD,
|
||||
HAND_PLAY,
|
||||
HAND_PLAYING
|
||||
|
|
@ -50,8 +71,13 @@ enum HandState
|
|||
|
||||
enum PlayState
|
||||
{
|
||||
PLAY_PLAYING,
|
||||
PLAY_SCORING,
|
||||
PLAY_STARTING,
|
||||
PLAY_BEFORE_SCORING,
|
||||
PLAY_SCORING_CARDS,
|
||||
PLAY_SCORING_CARD_JOKERS,
|
||||
PLAY_SCORING_HELD_CARDS,
|
||||
PLAY_SCORING_INDEPENDENT_JOKERS,
|
||||
PLAY_SCORING_HAND_SCORED_END,
|
||||
PLAY_ENDING,
|
||||
PLAY_ENDED
|
||||
};
|
||||
|
|
@ -64,10 +90,10 @@ enum HandType
|
|||
PAIR,
|
||||
TWO_PAIR,
|
||||
THREE_OF_A_KIND,
|
||||
FOUR_OF_A_KIND,
|
||||
STRAIGHT,
|
||||
FLUSH,
|
||||
FULL_HOUSE,
|
||||
FOUR_OF_A_KIND,
|
||||
STRAIGHT_FLUSH,
|
||||
ROYAL_FLUSH,
|
||||
FIVE_OF_A_KIND,
|
||||
|
|
@ -75,27 +101,81 @@ enum HandType
|
|||
FLUSH_FIVE
|
||||
};
|
||||
|
||||
// clang-format off
|
||||
// Store all contained hands to optimize "whole hand condition" Jokers
|
||||
typedef struct ContainedHandTypes
|
||||
{
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
u16 HIGH_CARD : 1;
|
||||
u16 PAIR : 1;
|
||||
u16 TWO_PAIR : 1;
|
||||
u16 THREE_OF_A_KIND : 1;
|
||||
u16 STRAIGHT : 1;
|
||||
u16 FLUSH : 1;
|
||||
u16 FULL_HOUSE : 1;
|
||||
u16 FOUR_OF_A_KIND : 1;
|
||||
u16 STRAIGHT_FLUSH : 1;
|
||||
u16 ROYAL_FLUSH : 1;
|
||||
u16 FIVE_OF_A_KIND : 1;
|
||||
u16 FLUSH_HOUSE : 1;
|
||||
u16 FLUSH_FIVE : 1;
|
||||
u16 : 3;
|
||||
};
|
||||
u16 value;
|
||||
};
|
||||
} ContainedHandTypes;
|
||||
// clang-format on
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int substate;
|
||||
void (*on_init)();
|
||||
void (*on_update)();
|
||||
void (*on_exit)();
|
||||
} StateInfo;
|
||||
|
||||
// Game functions
|
||||
void game_init();
|
||||
void game_update();
|
||||
void game_set_state(enum GameState new_game_state);
|
||||
void game_change_state(enum GameState new_game_state);
|
||||
|
||||
// Forward declaration
|
||||
struct List;
|
||||
typedef struct List List;
|
||||
CardObject** get_hand_array(void);
|
||||
int get_hand_top(void);
|
||||
int hand_get_size(void);
|
||||
CardObject** get_played_array(void);
|
||||
int get_played_top(void);
|
||||
int get_scored_card_index(void);
|
||||
bool is_joker_owned(int joker_id);
|
||||
bool card_is_face(Card* card);
|
||||
List* get_jokers_list(void);
|
||||
List* get_expired_jokers_list(void);
|
||||
|
||||
// Utility functions for other files
|
||||
typedef struct CardObject CardObject; // forward declaration, actually declared in card.h
|
||||
typedef struct JokerObject JokerObject;
|
||||
CardObject** get_hand_array(void);
|
||||
int get_hand_top(void);
|
||||
int hand_get_size(void);
|
||||
CardObject** get_played_array(void);
|
||||
int get_played_top(void);
|
||||
List* get_jokers(void);
|
||||
ContainedHandTypes* get_contained_hands(void);
|
||||
enum HandType* get_hand_type(void);
|
||||
|
||||
int get_deck_top(void);
|
||||
int get_num_discards_remaining(void);
|
||||
int get_num_hands_remaining(void);
|
||||
|
||||
u32 get_chips(void);
|
||||
void set_chips(u32 new_chips);
|
||||
void display_chips();
|
||||
u32 get_mult(void);
|
||||
void set_mult(u32 new_mult);
|
||||
void display_mult();
|
||||
int get_money(void);
|
||||
void set_money(int new_money);
|
||||
void display_money();
|
||||
void set_retrigger(bool new_retrigger);
|
||||
|
||||
int get_game_speed(void);
|
||||
void set_game_speed(int new_game_speed);
|
||||
|
||||
// joker specific functions
|
||||
bool is_shortcut_joker_active(void);
|
||||
int get_straight_and_flush_size(void);
|
||||
|
||||
#endif // GAME_H
|
||||
|
|
|
|||
9
include/gbalatro_sys8.h
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
#ifndef GBALATRO_SYS8_FONT_H
|
||||
#define GBALATRO_SYS8_FONT_H
|
||||
|
||||
#include <tonc.h>
|
||||
|
||||
extern const TFont gbalatro_sys8Font;
|
||||
extern const unsigned int gbalatro_sys8Glyphs[192];
|
||||
|
||||
#endif
|
||||
|
|
@ -1,182 +1,360 @@
|
|||
#ifndef GRAPHIC_UTILS_H
|
||||
#define GRAPHIC_UTILS_H
|
||||
|
||||
#include <tonc_video.h>
|
||||
|
||||
/* This file contains general utils and wrappers that relate to
|
||||
/**
|
||||
* @file graphic_utils.h
|
||||
*
|
||||
* @brief Graphic utility functions
|
||||
*
|
||||
* This file contains general utils and wrappers that relate to
|
||||
* graphics/video/vram and generally displaying things on the screen.
|
||||
* Mostly wrappers and defines for using tonc.
|
||||
*
|
||||
* Note: the code here assumes we're working with a single screenblock
|
||||
* which should be true for this entire game since a screenblock
|
||||
*
|
||||
* Note: the code here assumes we're working with a single screenblock
|
||||
* which should be true for this entire game since a screenblock
|
||||
* is enough to contain a full screen (and more)
|
||||
* and there isn't any scrolling etc.
|
||||
*/
|
||||
#ifndef GRAPHIC_UTILS_H
|
||||
#define GRAPHIC_UTILS_H
|
||||
|
||||
/* Reminder:
|
||||
#include <tonc_math.h>
|
||||
#include <tonc_video.h>
|
||||
|
||||
/**
|
||||
* @name Graphics Utilities Constants
|
||||
*
|
||||
* Reminder:
|
||||
* Screen Base Block is the base for the screenblock entries i.e. tilemap
|
||||
* Character Base Block is the base for the tiles themselves
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
#define MAIN_BG_SBB 31
|
||||
#define MAIN_BG_CBB 1
|
||||
#define TTE_SBB 30
|
||||
#define TTE_CBB 0
|
||||
|
||||
#define MAIN_BG_SBB 31
|
||||
#define MAIN_BG_CBB 1
|
||||
#define TTE_SBB 30
|
||||
#define TTE_CBB 0
|
||||
#define AFFINE_BG_SBB 2
|
||||
#define AFFINE_BG_CBB 2
|
||||
#define PAL_ROW_LEN 16
|
||||
#define NUM_PALETTES 16
|
||||
#define PAL_ROW_LEN 16
|
||||
#define NUM_PALETTES 16
|
||||
|
||||
/**
|
||||
* @def TILE_SIZE
|
||||
* @brief Tile size in pixels, both height and width as tiles are square
|
||||
*/
|
||||
#define TILE_SIZE 8
|
||||
|
||||
/**
|
||||
* @def TILE_MEM_OBJ_CHARBLOCK0_IDX
|
||||
* @brief The index for the first charblock of the object memory in tonc's tile_mem
|
||||
*/
|
||||
#define TILE_MEM_OBJ_CHARBLOCK0_IDX 4
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name TTE Palette constants
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** @def TTE_BIT_UNPACK_OFFSET */
|
||||
#define TTE_BIT_UNPACK_OFFSET 14
|
||||
|
||||
/** @def TTE_BIT_ON_CLR_IDX */
|
||||
#define TTE_BIT_ON_CLR_IDX TTE_BIT_UNPACK_OFFSET + 1
|
||||
|
||||
#define TTE_YELLOW_PB 12 // 0xC
|
||||
#define TTE_BLUE_PB 13 // 0xD
|
||||
#define TTE_RED_PB 14 // 0xE
|
||||
#define TTE_WHITE_PB 15 // 0xF
|
||||
/** @def TTE_YELLOW_PB */
|
||||
#define TTE_YELLOW_PB 12 // 0xC
|
||||
|
||||
#define TEXT_CLR_YELLOW RGB15(31, 20, 0) // 0x029F
|
||||
#define TEXT_CLR_BLUE RGB15(0, 18, 31) // 0x7E40
|
||||
#define TEXT_CLR_RED RGB15(31, 9, 8) // 0x213F
|
||||
#define TEXT_CLR_WHITE CLR_WHITE
|
||||
/** @def TTE_BLUE_PB */
|
||||
#define TTE_BLUE_PB 13 // 0xD
|
||||
|
||||
/* Dimensions for a screenblock.
|
||||
/** @def TTE_RED_PB */
|
||||
#define TTE_RED_PB 14 // 0xE
|
||||
|
||||
/** @def TTE_WHITE_PB */
|
||||
#define TTE_WHITE_PB 15 // 0xF
|
||||
|
||||
/** @def TTE_SPECIAL_PB_MULT_OFFSET */
|
||||
#define TTE_SPECIAL_PB_MULT_OFFSET 0x1000
|
||||
|
||||
/**
|
||||
* @def TTE_CHAR_SIZE
|
||||
*
|
||||
* @brief By default TTE characters occupy a single tile
|
||||
*/
|
||||
#define TTE_CHAR_SIZE TILE_SIZE
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name Text colors
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** @def TEXT_CLR_YELLOW */
|
||||
#define TEXT_CLR_YELLOW RGB15(31, 20, 0) // 0x029F
|
||||
|
||||
/** @def TEXT_CLR_BLUE */
|
||||
#define TEXT_CLR_BLUE RGB15(0, 18, 31) // 0x7E40
|
||||
|
||||
/** @def TEXT_CLR_RED */
|
||||
#define TEXT_CLR_RED RGB15(31, 9, 8) // 0x213F
|
||||
|
||||
/** @def TEXT_CLR_WHITE */
|
||||
#define TEXT_CLR_WHITE CLR_WHITE
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name Dimensions for a screenblock.
|
||||
*
|
||||
* A 1024 size screenblock is arranged in a grid of 32x32 screen entries
|
||||
* Interestingly since each block is 8x8 pixels, the 240x160 GBA screen
|
||||
* is smaller than the screenblock, only the top left part of the screenblock
|
||||
* is displayed on the screen.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** @def SE_ROW_LEN */
|
||||
#define SE_ROW_LEN 32
|
||||
|
||||
/** @def SE_COL_LEN */
|
||||
#define SE_COL_LEN 32
|
||||
|
||||
// Since y direction goes from the top of the screen to the bottom
|
||||
#define SCREEN_UP -1
|
||||
#define SCREEN_DOWN 1
|
||||
#define SCREEN_LEFT -1
|
||||
#define SCREEN_RIGHT 1
|
||||
/** @} */
|
||||
|
||||
#define SE_UP SCREEN_UP
|
||||
#define SE_DOWN SCREEN_DOWN
|
||||
enum ScreenVertDir
|
||||
{
|
||||
SCREEN_UP = -1,
|
||||
SCREEN_DOWN = 1
|
||||
};
|
||||
|
||||
#define OVERFLOW_LEFT SCREEN_LEFT
|
||||
#define OVERFLOW_RIGHT SCREEN_RIGHT
|
||||
enum ScreenHorzDir
|
||||
{
|
||||
SCREEN_LEFT = -1,
|
||||
SCREEN_RIGHT = 1
|
||||
};
|
||||
|
||||
// Tile size in pixels, both height and width as tiles are square
|
||||
#define TILE_SIZE 8
|
||||
#define EFFECT_TEXT_SEPARATION_AMOUNT 32; // If we need to show multiple effects at once
|
||||
enum OverflowDir
|
||||
{
|
||||
OVERFLOW_LEFT = SCREEN_LEFT,
|
||||
OVERFLOW_RIGHT = SCREEN_RIGHT
|
||||
};
|
||||
|
||||
// By default TTE characters occupy a single tile
|
||||
#define TTE_CHAR_SIZE TILE_SIZE
|
||||
/** @} */
|
||||
|
||||
// When making this, missed that it already exists in tonc_math.h
|
||||
typedef RECT Rect;
|
||||
|
||||
/* Gets the screenblock entry for the given coordinates (x, y).
|
||||
* x and y are in number of tiles.
|
||||
* Returns the screenblock entry.
|
||||
/**
|
||||
* @brief Get the width of a rectangle
|
||||
*
|
||||
* @param rect a @ref Rect to measure
|
||||
*
|
||||
* @return The width of the rectangle.
|
||||
*/
|
||||
SE main_bg_se_get_se(BG_POINT pos);
|
||||
|
||||
INLINE int rect_width(const Rect* rect)
|
||||
{
|
||||
/* Extra parens to avoid issues in case compiler turns INLINE into macro
|
||||
* Not sure if necessary, could be just paranoia
|
||||
*/
|
||||
return (((rect)->right) - ((rect)->left) + 1);
|
||||
return max(0, rect->right - rect->left + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the height of a rectangle
|
||||
*
|
||||
* @param rect a @ref Rect to measure
|
||||
*
|
||||
* @return The height of the rectangle, or 0 if rect->right < rect->left
|
||||
*/
|
||||
INLINE int rect_height(const Rect* rect)
|
||||
{
|
||||
return (((rect)->bottom) - ((rect)->top) + 1);
|
||||
return max(0, rect->bottom - rect->top + 1);
|
||||
}
|
||||
|
||||
/* Copies an SE rect vertically in direction by a single tile.
|
||||
/**
|
||||
* @brief Copies an SE rect vertically in direction by a single tile.
|
||||
*
|
||||
* bg_sbb is the SBB of the background in which to move the rect
|
||||
* direction must be either SE_UP or SE_DOWN.
|
||||
* se_rect dimensions are in number of tiles.
|
||||
*
|
||||
*
|
||||
* NOTE: This does not work with TTE_SBB, probably because it's 4BPP...
|
||||
*
|
||||
* If you are doing this operation you are probably doing this in the main
|
||||
* background and you should use main_bg_se_copy_rect_1_tile_vert() instead.
|
||||
*
|
||||
* @param bg_sbb the SBB of the background in which to move the rect.
|
||||
*
|
||||
* @param se_rect dimensions are in number of tiles.
|
||||
*
|
||||
* @param direction must be either @ref SE_UP or @ref SE_DOWN.
|
||||
*/
|
||||
void bg_se_copy_rect_1_tile_vert(u16 bg_sbb, Rect se_rect, int direction);
|
||||
void bg_se_copy_rect_1_tile_vert(u16 bg_sbb, Rect se_rect, enum ScreenVertDir direction);
|
||||
|
||||
/* Clears a rect in the main background.
|
||||
* The se_rect dimensions need to be in number of tiles.
|
||||
/**
|
||||
* @brief Clears a rect in the main background.
|
||||
*
|
||||
* @param se_rect dimensions need to be in number of tiles.
|
||||
*/
|
||||
void main_bg_se_clear_rect(Rect se_rect);
|
||||
|
||||
/* Copies a rect in the main background vertically in direction by a single tile.
|
||||
* direction must be either SE_UP or SE_DOWN.
|
||||
* se_rect dimensions are in number of tiles.
|
||||
/**
|
||||
* @brief Copies a rect in the main background vertically in direction by a single tile.
|
||||
*
|
||||
* @param se_rect dimensions are in number of tiles.
|
||||
*
|
||||
* @param direction must be either @ref SE_UP or @ref SE_DOWN.
|
||||
*/
|
||||
void main_bg_se_copy_rect_1_tile_vert(Rect se_rect, int direction);
|
||||
void main_bg_se_copy_rect_1_tile_vert(Rect se_rect, enum ScreenVertDir direction);
|
||||
|
||||
/* Copies a rect in the main background from se_rect to the position (x, y).
|
||||
* se_rect dimensions are in number of tiles.
|
||||
* x and y are the coordinates in number of tiles.
|
||||
/**
|
||||
* @brief Copies a rect in the main background from se_rect to the position (x, y).
|
||||
*
|
||||
* @param se_rect dimensions are in number of tiles.
|
||||
*
|
||||
* @param dest_pos x and y are the coordinates in number of tiles.
|
||||
*/
|
||||
void main_bg_se_copy_rect(Rect se_rect, BG_POINT pos);
|
||||
void main_bg_se_copy_rect(Rect se_rect, BG_POINT dest_pos);
|
||||
|
||||
/* Copies a screen entry to a rect in the main background.
|
||||
* se_rect dimensions are in number of tiles.
|
||||
* The tile is copied to the top left corner of the rect.
|
||||
*/
|
||||
void main_bg_se_fill_rect_with_se(SE tile, Rect se_rect);
|
||||
|
||||
/* Copies a 3x3 rect into se_rect_dest, the 3x3 rect is stretched to fill se_rect_dest.
|
||||
* The corners are copied, the sides are stretched, and the center is filled.
|
||||
* The parameter se_rect_src_3x3_top_left points to the top left corner of the source
|
||||
* 3x3 rect.
|
||||
* Dest rect sides can be of length 2, then the sides are not copied, only the corners.
|
||||
* But dest rect sides must be at least 2.
|
||||
/**
|
||||
* @brief Copies a 3x3 rect and expands it to fit a passed rect.
|
||||
*
|
||||
* Performs the following operation:
|
||||
*
|
||||
* 1. The 3x3 rect is stretched to fill se_rect_dest.
|
||||
* 2. The corners are copied
|
||||
* 3. The sides are stretched
|
||||
* 4. The center is filled.
|
||||
*
|
||||
* @param se_rect_dest destination for the 3x3 copy, if rect sides are length 2, then the sides are
|
||||
* not copied, and the center is not filled (in the case of both top and bottom sides),
|
||||
* and only the corners are copied.
|
||||
* **But dest rect sides must be at least 2.**
|
||||
*
|
||||
* @param se_rect_src_3x3_top_left points to the top left corner of the source 3x3 rect.
|
||||
*/
|
||||
void main_bg_se_copy_expand_3x3_rect(Rect se_rect_dest, BG_POINT se_rect_src_3x3_top_left);
|
||||
|
||||
/* Moves a rect in the main background vertically in direction by a single tile.
|
||||
/**
|
||||
* @brief Copies a 3-width SE rect and expands it to fit a passed rect.
|
||||
*
|
||||
* Performs the following operation:
|
||||
*
|
||||
* 1. The sides are stretched
|
||||
* 2. The center is filled.
|
||||
*
|
||||
* @param se_rect_dest destination for the copy; if rect width is 2, the center is not
|
||||
* filled and only the sides are copied.
|
||||
* **But dest rect width must be at least 2.**
|
||||
*
|
||||
* @param src_row_left_pnt points to the leftmost tile of the source 3-width rect.
|
||||
*/
|
||||
void main_bg_se_copy_expand_3w_row(Rect se_dest_rect, BG_POINT src_row_left_pnt);
|
||||
|
||||
/**
|
||||
* @brief Moves a rect in the main background vertically in direction by a single tile.
|
||||
*
|
||||
* Note that tiles in the previous location will be transparent (0x000)
|
||||
* so maybe copy would be a better choice if you don't want to delete things
|
||||
* direction must be either SE_UP or SE_DOWN.
|
||||
* se_rect dimensions are in number of tiles.
|
||||
*
|
||||
* @param se_rect dimensions are in number of tiles.
|
||||
* @param direction must be either @ref SE_UP or @ref SE_DOWN.
|
||||
*/
|
||||
void main_bg_se_move_rect_1_tile_vert(Rect se_rect, int direction);
|
||||
void main_bg_se_move_rect_1_tile_vert(Rect se_rect, enum ScreenVertDir direction);
|
||||
|
||||
// A wrapper for tte_erase_rect that would use the rect struct
|
||||
/**
|
||||
* @brief A wrapper for tte_erase_rect that would use the rect struct
|
||||
*
|
||||
* @param rect rectangle to erase
|
||||
*/
|
||||
void tte_erase_rect_wrapper(Rect rect);
|
||||
|
||||
/* Changes rect->left so it fits the digits of num exactly when right aligned to rect->right.
|
||||
/**
|
||||
* @brief Changes rect->left so it fits the digits of num exactly when right aligned to rect->right.
|
||||
* Assumes num is not negative.
|
||||
*
|
||||
*
|
||||
* overflow_direction determines the direction the number will overflow
|
||||
* if it's too large to fit inside the rect.
|
||||
* Should be either OVERFLOW_LEFT or OVERFLOW_RIGHT.
|
||||
*
|
||||
* The rect is in number of pixels but should be a multiple of TILE_SIZE
|
||||
* so it's a whole number of tiles to fit TTE characters
|
||||
*
|
||||
* if it's too large to fit inside the rect.
|
||||
*
|
||||
* Note that both rect->left and rect-right need to be defined, top and bottom don't matter
|
||||
*
|
||||
* @param rect is in number of pixels but should be a multiple of TILE_SIZE, so it's a whole number
|
||||
* of tiles to fit TTE characters
|
||||
*
|
||||
* @param num number to display
|
||||
*
|
||||
* @param overflow_direction either OVERFLOW_LEFT or OVERFLOW_RIGHT.
|
||||
*/
|
||||
void update_text_rect_to_right_align_num(Rect* rect, int num, int overflow_direction);
|
||||
void update_text_rect_to_right_align_str(
|
||||
Rect* rect,
|
||||
const char* str,
|
||||
enum OverflowDir overflow_direction
|
||||
);
|
||||
|
||||
/*Copies 16 bit data from src to dst, applying a palette offset to the data.
|
||||
/**
|
||||
* @brief Updates a rect so a string is centered within it.
|
||||
*
|
||||
* @param rect The rect provided, the provided values are used to determine the center
|
||||
* and it is then updated so the string starting in rect->left is centered
|
||||
* The rect is in number of pixels but should be a multiple of TTE_CHAR_SIZE
|
||||
* so it's a whole number of tiles to fit TTE characters.
|
||||
*
|
||||
* @param str The string, the center of the string will be at the center of the updated rect.
|
||||
*
|
||||
* @param bias_direction Which direction to bias when the string can't be evenly centered
|
||||
* with respect to char tiles.
|
||||
* Examples:
|
||||
* | |S|T|R| | - Can be evenly centered, bias has no effect
|
||||
* | | |S|T|R| | - Bias right
|
||||
* | |S|T|R| | | - Bias left
|
||||
* |A|B|C|D| | - Bias left
|
||||
* | |A|B|C|D| - Bias right
|
||||
*/
|
||||
void update_text_rect_to_center_str(Rect* rect, const char* str, enum ScreenHorzDir bias_direction);
|
||||
|
||||
/**
|
||||
* @brief Copies 16 bit data from src to dst, applying a palette offset to the data.
|
||||
*
|
||||
* This is intended solely for use with tile8/8bpp data for dst and src.
|
||||
* The palette offset allows the tiles to use a different location in the palette
|
||||
* memory
|
||||
* This is useful because grit always loads the palette to the beginning of
|
||||
* pal_bg_mem[]
|
||||
* The palette offset allows the tiles to use a different location in the palette * memory
|
||||
* This is useful because grit always loads the palette to the beginning of pal_bg_mem[]
|
||||
*
|
||||
* @param dst destination charblock
|
||||
*
|
||||
* @param src destination charblock
|
||||
*
|
||||
* @param wcount Number of words to copy
|
||||
*
|
||||
* @param palette_offset palette offset to shift to
|
||||
*/
|
||||
void memcpy16_tile8_with_palette_offset(u16* dst, const u16* src, uint hwcount, u8 palette_offset);
|
||||
|
||||
/*Copies 32 bit data from src to dst, applying a palette offset to the data.
|
||||
/**
|
||||
* @brief Copies 32 bit data from src to dst, applying a palette offset to the data.
|
||||
*
|
||||
* This is intended solely for use with tile8/8bpp data for dst and src.
|
||||
* The palette offset allows the tiles to use a different location in the palette
|
||||
* memory
|
||||
* This is useful because grit always loads the palette to the beginning of
|
||||
* pal_bg_mem[]
|
||||
* The palette offset allows the tiles to use a different location in the palette memory
|
||||
* This is useful because grit always loads the palette to the beginning of pal_bg_mem[]
|
||||
*
|
||||
* @param dst destination charblock
|
||||
*
|
||||
* @param src destination charblock
|
||||
*
|
||||
* @param wcount Number of words to copy
|
||||
*
|
||||
* @param palette_offset palette offset to shift to
|
||||
*/
|
||||
void memcpy32_tile8_with_palette_offset(u32* dst, const u32* src, uint wcount, u8 palette_offset);
|
||||
|
||||
/* Toggles the visibility of the window layers.
|
||||
* win0 and win1 are the visibility states for the two windows.
|
||||
/**
|
||||
* @brief Toggles the visibility of the window layers.
|
||||
*
|
||||
* These windows are primarily used for the shadows on held jokers, consumables and cards.
|
||||
*
|
||||
* @param win0 the visibility state for window 0
|
||||
* @param win1 the visibility state for window 1
|
||||
*/
|
||||
void toggle_windows(bool win0, bool win1);
|
||||
|
||||
#endif //GRAPHIC_UTILS_H
|
||||
#endif // GRAPHIC_UTILS_H
|
||||
|
|
|
|||
|
|
@ -1,16 +1,42 @@
|
|||
#ifndef HAND_ANALYSIS_H
|
||||
#define HAND_ANALYSIS_H
|
||||
|
||||
#include <tonc.h>
|
||||
#include "card.h"
|
||||
|
||||
void get_hand_distribution(u8 *ranks_out, u8 *suits_out);
|
||||
void get_played_distribution(u8 *ranks_out, u8 *suits_out);
|
||||
#include <tonc.h>
|
||||
|
||||
u8 hand_contains_n_of_a_kind(u8 *ranks);
|
||||
bool hand_contains_two_pair(u8 *ranks);
|
||||
bool hand_contains_full_house(u8 *ranks);
|
||||
bool hand_contains_straight(u8 *ranks);
|
||||
bool hand_contains_flush(u8 *suits);
|
||||
/**
|
||||
* @brief Outputs the distribution of ranks and suits in the hand
|
||||
* @param ranks_out output - updated such as ranks_out[rank] is the number of cards of rank in the
|
||||
* hand. Must be of size NUM_RANKS.
|
||||
* @param suits_out output - updated such as suits_out[suit] is the number of cards if suit in the
|
||||
* hand Must be of size NUM_SUITS
|
||||
*/
|
||||
void get_hand_distribution(u8 ranks_out[NUM_RANKS], u8 suits_out[NUM_SUITS]);
|
||||
|
||||
#endif
|
||||
/**
|
||||
* @brief Outputs the distribution of ranks and suits in the played stack
|
||||
* @param ranks_out output - updated such as ranks_out[rank] is the number of cards of rank in the
|
||||
* played stack. Must be of size NUM_RANKS.
|
||||
* @param suits_out output - updated such as suits_out[suit] is the number of cards if suit in the
|
||||
* played stack. Must be of size NUM_SUITS
|
||||
*/
|
||||
void get_played_distribution(u8 ranks_out[NUM_RANKS], u8 suits_out[NUM_SUITS]);
|
||||
|
||||
u8 hand_contains_n_of_a_kind(u8* ranks);
|
||||
bool hand_contains_two_pair(u8* ranks);
|
||||
bool hand_contains_full_house(u8* ranks);
|
||||
bool hand_contains_straight(u8* ranks);
|
||||
bool hand_contains_flush(u8* suits);
|
||||
|
||||
int find_flush_in_played_cards(CardObject** played, int top, int min_len, bool* out_selection);
|
||||
int find_straight_in_played_cards(
|
||||
CardObject** played,
|
||||
int top,
|
||||
bool shortcut_active,
|
||||
int min_len,
|
||||
bool* out_selection
|
||||
);
|
||||
void select_paired_cards_in_hand(CardObject** played, int top, bool* selection);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
172
include/joker.h
|
|
@ -1,94 +1,182 @@
|
|||
#ifndef JOKER_H
|
||||
#define JOKER_H
|
||||
|
||||
#include <maxmod.h>
|
||||
|
||||
#include "sprite.h"
|
||||
#include "card.h"
|
||||
#include "game.h"
|
||||
#include "graphic_utils.h"
|
||||
#include "sprite.h"
|
||||
|
||||
#define JOKER_TID (MAX_HAND_SIZE + MAX_SELECTION_SIZE) * JOKER_SPRITE_OFFSET // Tile ID for the starting index in the tile memory
|
||||
#include <maxmod.h>
|
||||
|
||||
// This won't be more than the number of jokers in your current deck
|
||||
// plus the amount that can fit in the shop, 8 should be fine. For now...
|
||||
#define MAX_ACTIVE_JOKERS 8
|
||||
|
||||
#define MAX_DEFINABLE_JOKERS 150
|
||||
|
||||
// Tile ID for the starting index in the tile memory
|
||||
#define JOKER_TID (MAX_HAND_SIZE + MAX_SELECTION_SIZE) * JOKER_SPRITE_OFFSET
|
||||
#define JOKER_SPRITE_OFFSET 16 // Offset for the joker sprites
|
||||
#define JOKER_BASE_PB 4 // The starting palette index for the jokers
|
||||
#define JOKER_LAST_PB (NUM_PALETTES - 1)
|
||||
#define JOKER_BASE_PB 4 // The starting palette index for the jokers
|
||||
#define JOKER_LAST_PB (NUM_PALETTES - 1)
|
||||
// Currently allocating the rest of the palettes for the jokers.
|
||||
// This number needs to be decreased once we need to allocated palettes for other sprites
|
||||
// such as planet cards etc.
|
||||
|
||||
#define JOKER_STARTING_LAYER 27
|
||||
|
||||
#define BASE_EDITION 0
|
||||
#define FOIL_EDITION 1
|
||||
#define HOLO_EDITION 2
|
||||
#define POLY_EDITION 3
|
||||
#define BASE_EDITION 0
|
||||
#define FOIL_EDITION 1
|
||||
#define HOLO_EDITION 2
|
||||
#define POLY_EDITION 3
|
||||
#define NEGATIVE_EDITION 4
|
||||
|
||||
#define MAX_EDITIONS 5
|
||||
|
||||
#define COMMON_JOKER 0
|
||||
#define UNCOMMON_JOKER 1
|
||||
#define RARE_JOKER 2
|
||||
#define COMMON_JOKER 0
|
||||
#define UNCOMMON_JOKER 1
|
||||
#define RARE_JOKER 2
|
||||
#define LEGENDARY_JOKER 3
|
||||
|
||||
// Percent chance to get a joker of each rarity
|
||||
// Note that this deviates slightly from the Balatro wiki to allow legendary
|
||||
// jokers to appear without spectral cards implemented
|
||||
#define COMMON_JOKER_CHANCE 70
|
||||
#define UNCOMMON_JOKER_CHANCE 25
|
||||
#define RARE_JOKER_CHANCE 5
|
||||
#define LEGENDARY_JOKER_CHANCE 0
|
||||
|
||||
// These are the common Joker Events. Special Joker behaviour will be checked on a
|
||||
// Joker per Joker basis (see if it's there, then do something, e.g. Pareidolia, Baseball Card)
|
||||
enum JokerEvent
|
||||
{
|
||||
JOKER_EVENT_ON_JOKER_CREATED, // Triggers only once when the Joker is created, mainly used for
|
||||
// data initialization
|
||||
JOKER_EVENT_ON_HAND_PLAYED, // Triggers only once when the hand is played and before the cards
|
||||
// are scored
|
||||
JOKER_EVENT_ON_CARD_SCORED, // Triggers when a played card scores (e.g. Walkie Talkie,
|
||||
// Fibonnacci...)
|
||||
JOKER_EVENT_ON_CARD_SCORED_END, // Triggers after the card has finishd scoring (e.g. retrigger
|
||||
// Jokers)
|
||||
JOKER_EVENT_ON_CARD_HELD, // Triggers when going through held cards
|
||||
JOKER_EVENT_INDEPENDENT, // Joker will trigger normally, when Jokers are scored (e.g. base
|
||||
// Joker)
|
||||
JOKER_EVENT_ON_HAND_SCORED_END, // Triggers when entire hand has finished scoring (e.g. food
|
||||
// Jokers)
|
||||
JOKER_EVENT_ON_HAND_DISCARDED, // Triggers when discarding a hand
|
||||
JOKER_EVENT_ON_ROUND_END, // Triggers at the end of the round (e.g. Rocket)
|
||||
JOKER_EVENT_ON_BLIND_SELECTED, // Triggers when selecting a blind (e.g. Dagger, Riff Raff,
|
||||
// Madness..)
|
||||
};
|
||||
|
||||
// These are flags that can be combined into a single u32 and returned by
|
||||
// JokerEffect functions to indicate which fields of the output JokerEffect are valid
|
||||
|
||||
#define JOKER_EFFECT_FLAG_NONE 0
|
||||
#define JOKER_EFFECT_FLAG_CHIPS (1 << 0)
|
||||
#define JOKER_EFFECT_FLAG_MULT (1 << 1)
|
||||
#define JOKER_EFFECT_FLAG_XMULT (1 << 2)
|
||||
#define JOKER_EFFECT_FLAG_MONEY (1 << 3)
|
||||
#define JOKER_EFFECT_FLAG_RETRIGGER (1 << 4)
|
||||
#define JOKER_EFFECT_FLAG_EXPIRE (1 << 5)
|
||||
#define JOKER_EFFECT_FLAG_MESSAGE (1 << 6)
|
||||
|
||||
#define MAX_JOKER_OBJECTS 32 // The maximum number of joker objects that can be created at once
|
||||
|
||||
#define DEFAULT_JOKER_ID 0
|
||||
#define GREEDY_JOKER_ID 1 // This is just an example to show the patern of making joker IDs
|
||||
#define JOKER_STENCIL_ID 16
|
||||
// Jokers in the game
|
||||
#define DEFAULT_JOKER_ID 0
|
||||
#define GREEDY_JOKER_ID 1
|
||||
#define STENCIL_JOKER_ID 16
|
||||
#define SHORTCUT_JOKER_ID 26
|
||||
#define PAREIDOLIA_JOKER_ID 30
|
||||
#define BLUEPRINT_JOKER_ID 39
|
||||
#define BRAINSTORM_JOKER_ID 40
|
||||
#define FOUR_FINGERS_JOKER_ID 48
|
||||
|
||||
typedef struct
|
||||
typedef struct
|
||||
{
|
||||
u8 id; // Unique ID for the joker, used to identify different jokers
|
||||
u8 id; // Unique ID for the joker, used to identify different jokers
|
||||
u8 modifier; // base, foil, holo, poly, negative
|
||||
u8 value;
|
||||
u8 rarity;
|
||||
bool processed;
|
||||
|
||||
// General purpose values that are interpreted differently for each Joker (scaling, last
|
||||
// retriggered card, etc...)
|
||||
s32 scoring_state;
|
||||
s32 persistent_state;
|
||||
} Joker;
|
||||
|
||||
typedef struct JokerObject
|
||||
{
|
||||
Joker *joker;
|
||||
Joker* joker;
|
||||
SpriteObject* sprite_object;
|
||||
} JokerObject;
|
||||
|
||||
typedef struct // These jokers are triggered after the played hand has finished scoring.
|
||||
typedef struct // These jokers are triggered after the played hand has finished scoring.
|
||||
{
|
||||
int chips;
|
||||
int mult;
|
||||
int xmult;
|
||||
u32 chips;
|
||||
u32 mult;
|
||||
u32 xmult;
|
||||
int money;
|
||||
bool retrigger; // Retrigger played hand (e.g. "Dusk" joker, even though on the wiki it says "On Scored" it makes more sense to have it here)
|
||||
bool retrigger; // Retrigger played hand (e.g. "Dusk" joker, even though on the wiki it says "On
|
||||
// Scored" it makes more sense to have it here)
|
||||
bool expire; // Will make the Joker expire/destry itself if true (i.e. Bananas and fully
|
||||
// consumed Food Jokers)
|
||||
char* message; // Used to send custom messages e.g. "Extinct!" or "Again!"
|
||||
} JokerEffect;
|
||||
|
||||
typedef JokerEffect (*JokerEffectFunc)(Joker *joker, Card *scored_card);
|
||||
typedef struct {
|
||||
// JokerEffectFuncs take in a joker that will be scored, a scored_card that is not NULL when related
|
||||
// to the given joker_event, and output a joker_effect storing the effects of the scored joker They
|
||||
// return a set of flags indicating what fields of the joker_effect are valid to access
|
||||
typedef u32 (*JokerEffectFunc)(
|
||||
Joker* joker,
|
||||
Card* scored_card,
|
||||
enum JokerEvent joker_event,
|
||||
JokerEffect** joker_effect
|
||||
);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
u8 rarity;
|
||||
u8 base_value;
|
||||
JokerEffectFunc effect;
|
||||
JokerEffectFunc joker_effect_func;
|
||||
} JokerInfo;
|
||||
const JokerInfo* get_joker_registry_entry(int joker_id);
|
||||
size_t get_joker_registry_size(void);
|
||||
|
||||
void joker_init();
|
||||
|
||||
Joker *joker_new(u8 id);
|
||||
void joker_destroy(Joker **joker);
|
||||
Joker* joker_new(u8 id);
|
||||
void joker_destroy(Joker** joker);
|
||||
|
||||
// Unique effects like "Four Fingers" or "Credit Card" will be hard coded into game.c with a conditional check for the joker ID from the players owned jokers
|
||||
// game.c should probably be restructured so most of the variables in it are moved to some sort of global variable header file so they can be easily accessed and modified for the jokers
|
||||
JokerEffect joker_get_score_effect(Joker *joker, Card *scored_card);
|
||||
// Unique effects like "Four Fingers" or "Credit Card" will be hard coded into game.c with a
|
||||
// conditional check for the joker ID from the players owned jokers game.c should probably be
|
||||
// restructured so most of the variables in it are moved to some sort of global variable header file
|
||||
// so they can be easily accessed and modified for the jokers
|
||||
u32 joker_get_score_effect(
|
||||
Joker* joker,
|
||||
Card* scored_card,
|
||||
enum JokerEvent joker_event,
|
||||
JokerEffect** joker_effect
|
||||
);
|
||||
int joker_get_sell_value(const Joker* joker);
|
||||
|
||||
JokerObject *joker_object_new(Joker *joker);
|
||||
void joker_object_destroy(JokerObject **joker_object);
|
||||
void joker_object_update(JokerObject *joker_object);
|
||||
void joker_object_shake(JokerObject *joker_object, mm_word sound_id); // This doesn't actually score anything, it just performs an animation and plays a sound effect
|
||||
bool joker_object_score(JokerObject *joker_object, Card* scored_card, int *chips, int *mult, int *xmult, int *money, bool *retrigger); // This scores the joker and returns true if it was scored successfully (Card = NULL means the joker is independent and not scored by a card)
|
||||
|
||||
void joker_object_set_selected(JokerObject* joker_object, bool selected);
|
||||
bool joker_object_is_selected(JokerObject* joker_object);
|
||||
JokerObject* joker_object_new(Joker* joker);
|
||||
void joker_object_destroy(JokerObject** joker_object);
|
||||
void joker_object_update(JokerObject* joker_object);
|
||||
// This doesn't actually score anything, it just performs an animation and plays a sound effect
|
||||
void joker_object_shake(JokerObject* joker_object, mm_word sound_id);
|
||||
// This scores the joker and returns true if it was scored successfully
|
||||
// card_object = NULL means the joker_event does not concern a particular Card, i.e. Independend or
|
||||
// On_Blind_Selected as opposed to events that concern a particular card, i.e. On_Card_Scored or
|
||||
// On_Card_Held
|
||||
bool joker_object_score(
|
||||
JokerObject* joker_object,
|
||||
CardObject* card_object,
|
||||
enum JokerEvent joker_event
|
||||
);
|
||||
|
||||
Sprite* joker_object_get_sprite(JokerObject* joker_object);
|
||||
int joker_get_random_rarity();
|
||||
|
||||
#endif // JOKER_H
|
||||
#endif // JOKER_H
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
// clang-format off
|
||||
#ifndef JOKER_GFX_H
|
||||
#define JOKER_GFX_H
|
||||
|
||||
|
|
@ -16,5 +17,17 @@
|
|||
#include "joker_gfx12.h"
|
||||
#include "joker_gfx13.h"
|
||||
#include "joker_gfx14.h"
|
||||
#include "joker_gfx15.h"
|
||||
#include "joker_gfx16.h"
|
||||
#include "joker_gfx17.h"
|
||||
#include "joker_gfx18.h"
|
||||
#include "joker_gfx19.h"
|
||||
#include "joker_gfx20.h"
|
||||
#include "joker_gfx21.h"
|
||||
#include "joker_gfx22.h"
|
||||
#include "joker_gfx23.h"
|
||||
#include "joker_gfx24.h"
|
||||
#include "joker_gfx25.h"
|
||||
|
||||
#endif
|
||||
#endif
|
||||
// clang-format on
|
||||
|
|
|
|||
296
include/list.h
|
|
@ -1,25 +1,271 @@
|
|||
#ifndef LIST_H
|
||||
#define LIST_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct List
|
||||
{
|
||||
void** _array;
|
||||
int size;
|
||||
int allocated_size;
|
||||
} List;
|
||||
|
||||
List *list_new(int init_size);
|
||||
void list_destroy(List **list);
|
||||
bool list_append(List *list, void *value);
|
||||
bool list_remove_by_idx(List *list, int index);
|
||||
void* list_get(List *list, int index);
|
||||
int list_get_size(List *list);
|
||||
bool list_remove_by_value(List *list, void *value);
|
||||
|
||||
bool int_list_append(List *list, intptr_t value);
|
||||
intptr_t int_list_get(List *list, int index);
|
||||
bool int_list_remove_by_value(List *list, intptr_t value);
|
||||
|
||||
#endif
|
||||
/**
|
||||
* @file list.h
|
||||
*
|
||||
* @brief A doubly-linked list
|
||||
*
|
||||
* List Implementation
|
||||
* ===================
|
||||
*
|
||||
* - This @ref List operates as a linked list @ref ListNodes. It operates as a regular
|
||||
* doubly-linked list but doesn't allocate memory and rather gets @ref ListNodes from a pool.
|
||||
*/
|
||||
#ifndef LIST_H
|
||||
#define LIST_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* @def MAX_LIST_NODES
|
||||
* @brief Number of reserved list nodes.
|
||||
*
|
||||
* Number of list nodes available from the pool of @ref ListNode . This should
|
||||
* be set to to the maximum number of list nodes needed at once.
|
||||
*/
|
||||
#define MAX_LIST_NODES 128
|
||||
|
||||
typedef struct ListNode ListNode;
|
||||
|
||||
/**
|
||||
* @brief A single entry in a @ref List
|
||||
*/
|
||||
struct ListNode
|
||||
{
|
||||
/**
|
||||
* @brief The previous @ref ListNode in the associated @ref List, NULL if at the `head` of the
|
||||
* list
|
||||
*/
|
||||
ListNode* prev;
|
||||
|
||||
/**
|
||||
* @brief The next @ref ListNode in the associated @ref List, NULL if at the `tail` of the list
|
||||
*/
|
||||
ListNode* next;
|
||||
|
||||
/**
|
||||
* @brief Pointer to generic data stored in this node
|
||||
*/
|
||||
void* data;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A doubly-linked list
|
||||
*/
|
||||
typedef struct List
|
||||
{
|
||||
/**
|
||||
* @brief The first entry in the list
|
||||
*/
|
||||
ListNode* head;
|
||||
|
||||
/**
|
||||
* @brief The last entry in the list
|
||||
*/
|
||||
ListNode* tail;
|
||||
|
||||
/**
|
||||
* @brief Number of elements in list
|
||||
*/
|
||||
int len;
|
||||
} List;
|
||||
|
||||
/**
|
||||
* @brief @ref ListItr direction
|
||||
*/
|
||||
enum ListItrDirection
|
||||
{
|
||||
LIST_ITR_FORWARD,
|
||||
LIST_ITR_REVERSE,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief An iterator into a list
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
/**
|
||||
* @brief A pointer to the @ref List this is iterating through
|
||||
*/
|
||||
List* list;
|
||||
|
||||
/**
|
||||
* @brief The next node in the list
|
||||
*/
|
||||
ListNode* next_node;
|
||||
|
||||
/**
|
||||
* @brief The current node in the list iterator
|
||||
*
|
||||
* The node of the most recently returned data from @ref list_itr_next() .
|
||||
*/
|
||||
ListNode* current_node;
|
||||
|
||||
/**
|
||||
* @brief The direction of the iterator
|
||||
*/
|
||||
enum ListItrDirection direction;
|
||||
} ListItr;
|
||||
|
||||
/**
|
||||
* Create a list.
|
||||
*
|
||||
* While this function does not allocate memory for the list itself, the list does allocate memory
|
||||
* for each element. So every created list must be freed with @ref list_clear to ensure the list's
|
||||
* nodes are deleted properly.
|
||||
*
|
||||
* @return A @ref List with head and tail reset.
|
||||
*/
|
||||
List list_create(void);
|
||||
|
||||
/**
|
||||
* Clear a list.
|
||||
*
|
||||
* Go through the list and free each node and set the `head` and `tail` to `NULL`.
|
||||
* Note, it doesn't "free" the data at the node.
|
||||
*
|
||||
* @param list pointer to a @ref List to clear
|
||||
*/
|
||||
void list_clear(List* list);
|
||||
|
||||
/**
|
||||
* Check if a list is empty
|
||||
*
|
||||
* @param list pointer to a @ref List
|
||||
*
|
||||
* @return `true` if the `list` is empty, `false` otherwise.
|
||||
*/
|
||||
bool list_is_empty(const List* list);
|
||||
|
||||
/**
|
||||
* Prepend an entry to the `head` of a @ref list
|
||||
*
|
||||
* @param list pointer to a @ref List
|
||||
* @param data pointer to data to put into the @ref List
|
||||
*/
|
||||
void list_push_front(List* list, void* data);
|
||||
|
||||
/**
|
||||
* Append an entry to the `tail` of a @ref list
|
||||
*
|
||||
* @param list pointer to a @ref List
|
||||
* @param data pointer to data to put into the @ref List
|
||||
*/
|
||||
void list_push_back(List* list, void* data);
|
||||
|
||||
/**
|
||||
* Insert data into a @ref List a specific index
|
||||
*
|
||||
* If the index specified is larger than the length of the list
|
||||
* it will @ref list_push_back() the data instead;
|
||||
*
|
||||
* Performs the following operation:
|
||||
*
|
||||
* ┌─────┐
|
||||
* │ node│
|
||||
* └─────┘
|
||||
* ┌─────┐ ┌─────┐ ┌─────┐
|
||||
* │idx-1│◄─►│ idx │◄─►│idx+1│
|
||||
* └─────┘ └─────┘ └─────┘
|
||||
*
|
||||
* 1. Set new `node` `prev` to the node at idx - 1
|
||||
* 2. Set new `node` `next` to the node at idx
|
||||
* 3. Set node at idx - 1 `next` to new `node`
|
||||
* 4. Set node at idx `prev` to the new `node`
|
||||
*
|
||||
* Result:
|
||||
*
|
||||
* ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐
|
||||
* │idx-1│◄─►│ node│◄─►│ idx │◄─►│idx+1│
|
||||
* └─────┘ └─────┘ └─────┘ └─────┘
|
||||
*
|
||||
* Finally, the list is now updated with new `node` now at the labeled idx:
|
||||
*
|
||||
* ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐
|
||||
* │idx-1│◄─►│ idx │◄─►│idx+1│◄─►│idx+2│
|
||||
* └─────┘ └─────┘ └─────┘ └─────┘
|
||||
*
|
||||
* @param list pointer to a @ref List
|
||||
* @param data pointer to data to put into the @ref List
|
||||
* @param idx desired index to insert
|
||||
*/
|
||||
void list_insert(List* list, void* data, unsigned int idx);
|
||||
|
||||
/**
|
||||
* Swap the data pointers at the specified indices of a @ref List
|
||||
*
|
||||
* If either indices are larger than the length of the list, return false.
|
||||
*
|
||||
* @param list pointer to a @ref List
|
||||
* @param idx_a desired index to swap with idx_b
|
||||
* @param idx_b desired index to swap with idx_a
|
||||
*
|
||||
* @return true if successful, false otherwise
|
||||
*/
|
||||
bool list_swap(List* list, unsigned int idx_a, unsigned int idx_b);
|
||||
|
||||
/**
|
||||
* Get a List's node at the specified index
|
||||
*
|
||||
* @param list pointer to a @ref List
|
||||
* @param idx index of the desired @ref ListNode in the list
|
||||
*
|
||||
* @return a pointer to the data at the index of the list, or NULL if out-of-bounds
|
||||
*/
|
||||
void* list_get_at_idx(List* list, unsigned int idx);
|
||||
|
||||
/**
|
||||
* Remove a List's node at the specified index
|
||||
*
|
||||
* @param list pointer to a @ref List
|
||||
* @param idx index of the desired @ref ListNode in the list
|
||||
*
|
||||
* @return `true` if successfully removed, `false` if out-of-bounds
|
||||
*/
|
||||
bool list_remove_at_idx(List* list, unsigned int idx);
|
||||
|
||||
/**
|
||||
* Get the number of elements in a @ref List
|
||||
*
|
||||
* @param list pointer to a @ref List
|
||||
*
|
||||
* @return The number of elements in the list
|
||||
*/
|
||||
int list_get_len(const List* list);
|
||||
|
||||
/**
|
||||
* Declare a @ref ListItr
|
||||
*
|
||||
* @param list pointer to a @ref List
|
||||
*
|
||||
* @return A new @ref ListItr
|
||||
*/
|
||||
ListItr list_itr_create(List* list);
|
||||
|
||||
/**
|
||||
* Declare a reverse @ref ListItr
|
||||
*
|
||||
* @param list pointer to a @ref List
|
||||
*
|
||||
* @return A new reverse @ref ListItr
|
||||
*/
|
||||
ListItr rev_list_itr_create(List* list);
|
||||
|
||||
/**
|
||||
* Get the next data entry in a @ref ListItr
|
||||
*
|
||||
* @param itr pointer to the @ref ListItr
|
||||
*
|
||||
* @return A pointer to the data pointer at the next @ref ListNode if valid, otherwise return NULL.
|
||||
*/
|
||||
void* list_itr_next(ListItr* itr);
|
||||
|
||||
/**
|
||||
* Remove the current @ref ListNode from the iterator.
|
||||
*
|
||||
* The "current node" corresponds to the list node associated with the
|
||||
* most recently returned valu from @ref list_itr_next()
|
||||
*
|
||||
* @param itr pointer to the @ref ListItr
|
||||
*/
|
||||
void list_itr_remove_current_node(ListItr* itr);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
67
include/pool.h
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
#ifndef POOL_H
|
||||
#define POOL_H
|
||||
|
||||
#include "bitset.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef POOLS_TEST_ENV
|
||||
#define POOLS_DEF_FILE "def_test_mempool.h"
|
||||
#else
|
||||
#define POOLS_DEF_FILE "def_balatro_mempool.h"
|
||||
#endif
|
||||
|
||||
#define POOL_DECLARE_TYPE(type) \
|
||||
typedef struct \
|
||||
{ \
|
||||
Bitset* bitset; \
|
||||
type* objects; \
|
||||
} type##Pool; \
|
||||
type* pool_get_##type(); \
|
||||
void pool_free_##type(type* obj); \
|
||||
int pool_idx_##type(type* obj); \
|
||||
type* pool_at_##type(int idx);
|
||||
|
||||
#define POOL_DEFINE_TYPE(type, capacity) \
|
||||
BITSET_DEFINE(type##_bitset, capacity) \
|
||||
static type type##_storage[capacity]; \
|
||||
static type##Pool type##_pool = { \
|
||||
.bitset = &type##_bitset, \
|
||||
.objects = type##_storage, \
|
||||
}; \
|
||||
type* pool_get_##type() \
|
||||
{ \
|
||||
int free_offset = bitset_set_next_free_idx(type##_pool.bitset); \
|
||||
if (free_offset == -1) \
|
||||
return NULL; \
|
||||
return &type##_pool.objects[free_offset]; \
|
||||
} \
|
||||
void pool_free_##type(type* entry) \
|
||||
{ \
|
||||
if (entry == NULL) \
|
||||
return; \
|
||||
int offset = entry - &type##_pool.objects[0]; \
|
||||
bitset_set_idx(type##_pool.bitset, offset, false); \
|
||||
} \
|
||||
int pool_idx_##type(type* entry) \
|
||||
{ \
|
||||
return entry - &type##_pool.objects[0]; \
|
||||
} \
|
||||
type* pool_at_##type(int idx) \
|
||||
{ \
|
||||
if (idx < 0 || idx >= (type##_pool.bitset)->cap) \
|
||||
return NULL; \
|
||||
return &type##_pool.objects[idx]; \
|
||||
}
|
||||
|
||||
#define POOL_GET(type) pool_get_##type()
|
||||
#define POOL_FREE(type, obj) pool_free_##type(obj)
|
||||
#define POOL_IDX(type, obj) pool_idx_##type(obj) // the index of the object
|
||||
#define POOL_AT(type, idx) pool_at_##type(idx) // the object at
|
||||
|
||||
#define POOL_ENTRY(name, capacity) POOL_DECLARE_TYPE(name);
|
||||
#include POOLS_DEF_FILE
|
||||
#undef POOL_ENTRY
|
||||
|
||||
#endif // POOL_H
|
||||
|
|
@ -1,5 +1,18 @@
|
|||
/**
|
||||
* @file selection_grid.h
|
||||
*
|
||||
* @brief An implementation for a selection grid that handles directional selection.
|
||||
*
|
||||
* The selection grid is not the same vertically and horizontally.
|
||||
* It is divided into rows and each row defines its own callbacks for size, directional changes,
|
||||
* and selection input.
|
||||
* The idea is that it would be a more fitting simple solution for this game since it tends
|
||||
* to be more dynamic horizontally than vertically with differing sizes of hands, jokers, etc.
|
||||
*/
|
||||
|
||||
#ifndef SELECTION_GRID_H
|
||||
#define SELECTION_GRID_H
|
||||
|
||||
#include <tonc.h>
|
||||
|
||||
typedef POINT Selection;
|
||||
|
|
@ -9,33 +22,138 @@ struct SelectionGrid;
|
|||
typedef struct SelectionGridRow SelectionGridRow;
|
||||
typedef struct SelectionGrid SelectionGrid;
|
||||
|
||||
// Called whenever there is a change in the selection cursor
|
||||
// row_idx is the index of the row whose function is invoked - can be used to identify whether it is the previous or new selection row.
|
||||
typedef void (*RowOnSelectionChangedFunc)(SelectionGrid* selection_grid, int row_idx, const Selection* prev_selection, const Selection* new_selection);
|
||||
typedef int (*RowGetSizeFunc)();
|
||||
// Called for any non-directional key hit
|
||||
// The key will not be passed, the function will have to check key_hit() etc. for the key it wants to check
|
||||
typedef void (*RowOnKeyHitFunc)(SelectionGrid* selection_grid, Selection* selection);
|
||||
/**
|
||||
* @brief Callback function type for handling selection changes in a SelectionGrid.
|
||||
*
|
||||
* This function is invoked whenever the selection cursor changes position within
|
||||
* the grid. Used to perform custom actions based on selection changes,
|
||||
* such as updating UI elements.
|
||||
*
|
||||
* @param selection_grid Pointer to the SelectionGrid instance where the change occurred
|
||||
* @param row_idx Index of the row associated with this callback. This can be used to
|
||||
* determine whether this is the previously selected row or the newly
|
||||
* selected row
|
||||
* @param prev_selection Pointer to the Selection state before the change occurred.
|
||||
* Contains the previous cursor position
|
||||
* @param new_selection Pointer to the Selection state after the change occurred.
|
||||
* Contains the new cursor position
|
||||
* @return false if the selection change needs to be aborted, true if can proceed.
|
||||
*
|
||||
*/
|
||||
typedef bool (*RowOnSelectionChangedFunc)(
|
||||
SelectionGrid* selection_grid,
|
||||
int row_idx,
|
||||
const Selection* prev_selection,
|
||||
const Selection* new_selection
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Function pointer type for retrieving the size of a row in a selection grid.
|
||||
*
|
||||
* This is useful to allow generic row lengths e.g. when it can depend on the number of cards
|
||||
* in hand, items in the shop, etc.
|
||||
*
|
||||
* @return int The number of elements in the row.
|
||||
*/
|
||||
typedef int (*RowGetSizeFunc)();
|
||||
|
||||
/**
|
||||
* @brief Callback function type for handling non-directional key presses in a selection grid row.
|
||||
*
|
||||
* This function is invoked whenever a non-directional key transitions
|
||||
* (either hit down or release up).
|
||||
* The specific key and the type of transition
|
||||
* (press/release) are not passed as parameters to the callback. Instead, the implementation must
|
||||
* query the key state using functions like key_hit(), key_released(), etc. to determine which key
|
||||
* triggered the event and its current state.
|
||||
*
|
||||
* @param selection_grid Pointer to the SelectionGrid that contains the row receiving the key event
|
||||
* @param selection Pointer to the Selection (row) that is handling the key transition.
|
||||
*/
|
||||
typedef void (*RowOnKeyTransitFunc)(SelectionGrid* selection_grid, Selection* selection);
|
||||
|
||||
/**
|
||||
* @brief A set of attributes to the selection grid row affecting selection grid behavior.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Whether to wrap selection when it passes the end of the row.
|
||||
*/
|
||||
bool wrap;
|
||||
} SelGridRowAttributes;
|
||||
|
||||
/**
|
||||
* @brief A single row in the selection grid, defined by its callback functions.
|
||||
*
|
||||
* @var row_idx is used to easily identify the row within the callbacks.
|
||||
*/
|
||||
struct SelectionGridRow
|
||||
{
|
||||
int row_idx;
|
||||
RowGetSizeFunc get_size;
|
||||
RowOnSelectionChangedFunc on_selection_changed;
|
||||
RowOnKeyHitFunc on_key_hit;
|
||||
RowGetSizeFunc get_size;
|
||||
RowOnSelectionChangedFunc on_selection_changed;
|
||||
RowOnKeyTransitFunc on_key_transit;
|
||||
SelGridRowAttributes attributes;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The core selection grid struct, represents the grid itself and its state.
|
||||
*
|
||||
* It can be statically defined with an array of @ref SelectionGridRow to define its
|
||||
* contents.
|
||||
*/
|
||||
struct SelectionGrid
|
||||
{
|
||||
SelectionGridRow* rows;
|
||||
const SelectionGridRow* rows;
|
||||
const int num_rows;
|
||||
Selection selection;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Processes user input for the selection grid.
|
||||
*
|
||||
* This function handles input events (such as directional controls and button presses)
|
||||
* and updates the selection grid's state accordingly. It should be called each frame
|
||||
* to respond to user interactions with the grid.
|
||||
*
|
||||
* @param selection_grid Pointer to the SelectionGrid structure to process input for.
|
||||
* Must not be NULL. NULL-checks are in place and will return early.
|
||||
*/
|
||||
void selection_grid_process_input(SelectionGrid* selection_grid);
|
||||
|
||||
/**
|
||||
* @brief Moves the selection horizontally within the selection grid.
|
||||
*
|
||||
* This function updates the current selection position by moving it horizontally
|
||||
* based on the specified direction. This can be useful if some non-press event
|
||||
* should update the selection grid.
|
||||
*
|
||||
* @param selection_grid Pointer to the SelectionGrid structure to operate on.
|
||||
* Must not be NULL. NULL-checks are in place and will return early.
|
||||
* @param direction_tribool Direction indicator for horizontal movement behaving like tonc's
|
||||
* tribools:
|
||||
* - Negative value: move left
|
||||
* - Zero: no movement
|
||||
* - Positive value: move right
|
||||
*/
|
||||
void selection_grid_move_selection_horz(SelectionGrid* selection_grid, int direction_tribool);
|
||||
|
||||
/**
|
||||
* @brief Moves the selection vertically within the selection grid.
|
||||
*
|
||||
* This function updates the current selection position by moving it vertically
|
||||
* based on the specified direction. This can be useful if some non-press event
|
||||
* should update the selection grid.
|
||||
*
|
||||
* @param selection_grid Pointer to the SelectionGrid structure to operate on.
|
||||
* Must not be NULL. NULL-checks are in place and will return early.
|
||||
* @param direction_tribool Direction indicator for vertical movement behaving like tonc's tribools:
|
||||
* - Negative value: move up
|
||||
* - Zero: no movement
|
||||
* - Positive value: move down
|
||||
*/
|
||||
void selection_grid_move_selection_vert(SelectionGrid* selection_grid, int direction_tribool);
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,13 +1,38 @@
|
|||
/**
|
||||
* @file splash_screen.h
|
||||
*
|
||||
* @brief Functions and constants for the splash screen intro
|
||||
*
|
||||
*/
|
||||
#ifndef SPLASH_SCREEN_H
|
||||
#define SPLASH_SCREEN_H
|
||||
|
||||
#include <tonc.h>
|
||||
|
||||
#define SPLASH_FPS 60
|
||||
/** @name Splash screen timing variables
|
||||
*
|
||||
* Constants for timing the duration of the splash screen.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
#define SPLASH_FPS 60
|
||||
#define SPLASH_DURATION_SECONDS 10
|
||||
#define SPLASH_DURATION_FRAMES (SPLASH_FPS * SPLASH_DURATION_SECONDS)
|
||||
#define SPLASH_DURATION_FRAMES (SPLASH_FPS * SPLASH_DURATION_SECONDS)
|
||||
/** @} */
|
||||
|
||||
void splash_screen_init();
|
||||
void splash_screen_update(uint timer);
|
||||
/**
|
||||
* @brief Initialize the splash screen by printing the splash screen text.
|
||||
*/
|
||||
void splash_screen_on_init(void);
|
||||
|
||||
#endif // SPLASH_SCREEN_H
|
||||
/**
|
||||
* @brief Update splash screen timers and print the remaining time accordingly.
|
||||
*/
|
||||
void splash_screen_on_update(void);
|
||||
|
||||
/**
|
||||
* @brief Exit the splash screen
|
||||
*/
|
||||
void splash_screen_on_exit(void);
|
||||
|
||||
#endif // SPLASH_SCREEN_H
|
||||
|
|
|
|||
353
include/sprite.h
|
|
@ -1,42 +1,332 @@
|
|||
/**
|
||||
* @file sprite.h
|
||||
*
|
||||
* @brief Sprite system for Gbalatro
|
||||
*/
|
||||
#ifndef SPRITE_H
|
||||
#define SPRITE_H
|
||||
|
||||
#include <tonc.h>
|
||||
#include <maxmod.h>
|
||||
#include <tonc.h>
|
||||
|
||||
#define CARD_SPRITE_SIZE 32
|
||||
/**
|
||||
* @name Sprite system constants
|
||||
* @{
|
||||
*/
|
||||
#define CARD_SPRITE_SIZE 32
|
||||
#define MAX_AFFINES 32
|
||||
#define MAX_SPRITES 128
|
||||
#define MAX_SPRITE_OBJECTS 16
|
||||
#define SPRITE_FOCUS_RAISE_PX 10
|
||||
#define CARD_FOCUS_SFX_PITCH_OFFSET_RANGE 512
|
||||
|
||||
typedef struct
|
||||
{
|
||||
OBJ_ATTR *obj;
|
||||
OBJ_AFFINE *aff;
|
||||
POINT pos;
|
||||
} Sprite;
|
||||
/** @} */
|
||||
|
||||
// A sprite object is a sprite that is selectable and movable in animation
|
||||
/**
|
||||
* @brief Sprite struct for GBA hardware specifics
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
/**
|
||||
* @brief GBA sprite attribute registers info (A0-A2)
|
||||
*/
|
||||
OBJ_ATTR* obj;
|
||||
|
||||
/**
|
||||
* @brief GBA sprite affine matrices registers info
|
||||
*/
|
||||
OBJ_AFFINE* aff;
|
||||
|
||||
/**
|
||||
* @brief Sprite position on screen in pixels
|
||||
*/
|
||||
POINT pos;
|
||||
|
||||
/**
|
||||
* @brief Sprite index in memory managed by GBAlatro
|
||||
*/
|
||||
int idx;
|
||||
} Sprite;
|
||||
|
||||
/**
|
||||
* @brief A sprite object is a sprite that is focusable and movable in animation
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
/**
|
||||
* @brief Sprite configuration info
|
||||
*/
|
||||
Sprite* sprite;
|
||||
FIXED tx, ty; // target position
|
||||
FIXED x, y; // position
|
||||
FIXED vx, vy; // velocity
|
||||
|
||||
/**
|
||||
* @brief Target position
|
||||
*/
|
||||
FIXED tx, ty;
|
||||
|
||||
/**
|
||||
* @brief Current position
|
||||
*/
|
||||
FIXED x, y;
|
||||
|
||||
/**
|
||||
* @brief Velocity
|
||||
*/
|
||||
FIXED vx, vy;
|
||||
|
||||
/**
|
||||
* @brief Target Scale
|
||||
*/
|
||||
FIXED tscale;
|
||||
|
||||
/**
|
||||
* @brief Current Scale, in units for tonc's `obj_aff_rotscale`
|
||||
*/
|
||||
FIXED scale;
|
||||
|
||||
/**
|
||||
* @brief Scale velocity AKA the rate of change of scaling ops
|
||||
*/
|
||||
FIXED vscale;
|
||||
FIXED trotation; // this never gets used so i might remove it later
|
||||
|
||||
/**
|
||||
* @brief Target rotation
|
||||
*/
|
||||
FIXED trotation;
|
||||
|
||||
/**
|
||||
* @brief Actual rotation, in units for tonc's `obj_aff_rotscale`
|
||||
*/
|
||||
FIXED rotation;
|
||||
|
||||
/**
|
||||
* @brief Rotation velocity
|
||||
*/
|
||||
FIXED vrotation;
|
||||
bool selected;
|
||||
|
||||
/**
|
||||
* @brief Focused status (card specific, raise and lower card)
|
||||
*/
|
||||
bool focused;
|
||||
|
||||
} SpriteObject;
|
||||
|
||||
// Sprite methods
|
||||
Sprite *sprite_new(u16 a0, u16 a1, u32 tid, u32 pb, int sprite_index);
|
||||
Sprite *affine_sprite_new(u16 a0, u16 a1, u32 tid, u32 pb);
|
||||
void sprite_destroy(Sprite **sprite);
|
||||
int sprite_get_layer(Sprite *sprite);
|
||||
INLINE void sprite_position(Sprite *sprite, int x, int y)
|
||||
/**
|
||||
* @brief Allocate and retrieve a pointer to a valid Sprite
|
||||
*
|
||||
* @param a0 attribute 0 of OBJ_ATTR
|
||||
* @param a1 attribute 1 of OBJ_ATTR
|
||||
* @param tid base tile index of sprite, part of attribute 2
|
||||
* @param pb Palette-bank
|
||||
* @param sprite_index index in memory
|
||||
*
|
||||
* @return Valid Sprite if allocations are successful.
|
||||
* Otherwise, return **NULL**.
|
||||
*/
|
||||
Sprite* sprite_new(u16 a0, u16 a1, u32 tid, u32 pb, int sprite_index);
|
||||
|
||||
/**
|
||||
* @brief Destroy Sprite
|
||||
*
|
||||
* @param sprite pointer to a pointer of Sprite to destroy. No action if **NULL**.
|
||||
*/
|
||||
void sprite_destroy(Sprite** sprite);
|
||||
|
||||
/**
|
||||
* @brief Get index of Sprite in the GBA object buffer
|
||||
*
|
||||
* @param sprite pointer to Sprite, cannot be **NULL**
|
||||
*
|
||||
* @return Index of sprite in object buffer if `sprite` is valid, otherwise **UNDEFINED**.
|
||||
*/
|
||||
int sprite_get_layer(Sprite* sprite);
|
||||
|
||||
/**
|
||||
* @brief Get a Sprite's width and height
|
||||
*
|
||||
* @param sprite pointer to Sprite, cannot be **NULL**
|
||||
* @param width pointer to variable to be set, cannot be **NULL**
|
||||
* @param height pointer to variable to be set, cannot be **NULL**
|
||||
*
|
||||
* @return **true** if successful, **false** if otherwise. Upon success,
|
||||
* `width` and `height` contain valid data, otherwise, the
|
||||
* variables are unchanged.
|
||||
*/
|
||||
bool sprite_get_dimensions(Sprite* sprite, int* width, int* height);
|
||||
|
||||
/**
|
||||
* @brief Get a Sprites's height
|
||||
*
|
||||
* @param sprite pointer to Sprite, cannot be **NULL**
|
||||
* @param height pointer to variable to be set, cannot be **NULL**
|
||||
*
|
||||
* @return **true** is successful, **false** if otherwise. Upon success,
|
||||
* `height` contains valid data, otherwise, the variable is unchanged.
|
||||
*/
|
||||
bool sprite_get_height(Sprite* sprite, int* height);
|
||||
|
||||
/**
|
||||
* @brief Get a Sprite's width
|
||||
*
|
||||
* @param sprite pointer to Sprite, cannot be **NULL**
|
||||
* @param width pointer to variable to be set, cannot be **NULL**
|
||||
*
|
||||
* @return **true** is successful, **false** if otherwise. Upon success,
|
||||
* `width` contains valid data, otherwise, the variable is unchanged.
|
||||
*/
|
||||
bool sprite_get_width(Sprite* sprite, int* width);
|
||||
|
||||
/**
|
||||
* @brief Get the palette bank of a Sprite
|
||||
*
|
||||
* @param sprite pointer to extract associated palette bank. Cannot be **NULL**.
|
||||
*
|
||||
* @return The palette bank of the Sprite if successful, otherwise return **UNDEFINED**.
|
||||
*/
|
||||
int sprite_get_pb(const Sprite* sprite);
|
||||
|
||||
/**
|
||||
* @brief Initialize GBAlatro sprite system
|
||||
*/
|
||||
void sprite_init(void);
|
||||
|
||||
/**
|
||||
* @brief Draw Sprites to screen, to be called once per frame
|
||||
*/
|
||||
void sprite_draw(void);
|
||||
|
||||
/**
|
||||
* @brief Allocate and retrieve a pointer to a valid SpriteObject
|
||||
*
|
||||
* @return A valid pointer to an newly allocated SpriteObject
|
||||
* if successful, othewise return **NULL**.
|
||||
*/
|
||||
SpriteObject* sprite_object_new();
|
||||
|
||||
/**
|
||||
* @brief Destroy SpriteObject
|
||||
*
|
||||
* Destroy a SpriteObject by freeing it back to the pool and releasing its
|
||||
* associated resources
|
||||
*
|
||||
* @param sprite_object pointer to a pointer of SpriteObject to destroy.
|
||||
* Cannot be **NULL**.
|
||||
*/
|
||||
void sprite_object_destroy(SpriteObject** sprite_object);
|
||||
|
||||
/**
|
||||
* @brief Register a Sprite to an associated SpriteObject
|
||||
*
|
||||
* @param sprite_object pointer to SpriteObject to associate Sprite with.
|
||||
* Cannot be **NULL**.
|
||||
*
|
||||
* @param sprite pointer to Sprite to associate SpriteObject with.
|
||||
* Cannot be **NULL**.
|
||||
*/
|
||||
void sprite_object_set_sprite(SpriteObject* sprite_object, Sprite* sprite);
|
||||
|
||||
/**
|
||||
* @brief Reset SpriteObject's transform back to default values.
|
||||
*
|
||||
* @param sprite_object pointer to SpriteObject to reset transform.
|
||||
* Cannot be **NULL**.
|
||||
*/
|
||||
void sprite_object_reset_transform(SpriteObject* sprite_object);
|
||||
|
||||
/**
|
||||
* @brief Update a SpriteObject, to be called once per frame per active SpriteObject
|
||||
*
|
||||
* @param sprite_object pointer to SpriteObject to update. Cannot be **NULL**.
|
||||
*/
|
||||
void sprite_object_update(SpriteObject* sprite_object);
|
||||
|
||||
/**
|
||||
* @brief Shake SpriteObject on screen and play a sound
|
||||
*
|
||||
* @param SpriteObject pointer to SpriteObject to shake. Cannot be **NULL**.
|
||||
* @param sound_id ID of sound from maxmod to play on executing shake. If **UNDEFINED**
|
||||
* no sound will play.
|
||||
*/
|
||||
void sprite_object_shake(SpriteObject* sprite_object, mm_word sound_id);
|
||||
|
||||
/**
|
||||
* @brief Get a SpriteObject's registered Sprite
|
||||
*
|
||||
* @param sprite_object pointer to SpriteObject's registered Sprite. Cannot be **NULL**.
|
||||
*
|
||||
* @return Sprite pointer registered to `sprite_object` if successful,
|
||||
* otherwise return **NULL**. May be successful and **NULL** if there is no
|
||||
* Sprite registered to the SpriteObject.
|
||||
*/
|
||||
Sprite* sprite_object_get_sprite(SpriteObject* sprite_object);
|
||||
|
||||
/**
|
||||
* @brief Set the focus for SpriteObject
|
||||
* Raises the object by SPRITE_FOCUS_RAISE_PX.
|
||||
*
|
||||
* Note: This is currently unused by CardObject as their focus is handled in
|
||||
* cards_in_hand_update_loop() but we may want to extract it from there and refactor them use this
|
||||
* instead.
|
||||
*
|
||||
* @param sprite_object pointer to SpriteObject to set the focus of. Cannot be **NULL**.
|
||||
* @param focus **true** to focus, **false** to unfocus
|
||||
*/
|
||||
void sprite_object_set_focus(SpriteObject* sprite_object, bool focus);
|
||||
|
||||
/**
|
||||
* @brief Get the width and height of SpriteObject's registered Sprite
|
||||
*
|
||||
* @param sprite_object pointer to SpriteObject to get the dimensions of. Cannot be **NULL**.
|
||||
* @param width pointer to variable to be set, cannot be **NULL**
|
||||
* @param height pointer to variable to be set, cannot be **NULL**
|
||||
*
|
||||
* @return **true** is successful, **false** if otherwise. Upon success,
|
||||
* `width` and `height` contain valid data, otherwise, the
|
||||
* variables are unchanged.
|
||||
*/
|
||||
bool sprite_object_get_dimensions(SpriteObject* sprite_object, int* width, int* height);
|
||||
|
||||
/**
|
||||
* @brief Get a SpriteObject's height
|
||||
*
|
||||
* @param sprite_object pointer to SpriteObject to get the height of. Cannot be **NULL**.
|
||||
* @param height pointer to variable to be set, cannot be **NULL**
|
||||
*
|
||||
* @return **true** is successful, **false** if otherwise. Upon success,
|
||||
* `height` contains valid data, otherwise, the
|
||||
* variable is unchanged.
|
||||
*/
|
||||
bool sprite_object_get_height(SpriteObject* sprite_object, int* height);
|
||||
|
||||
/**
|
||||
* @brief Get a SpriteObject's width
|
||||
*
|
||||
* @param sprite_object pointer to SpriteObject to get the width of. Cannot be **NULL**.
|
||||
* @param width pointer to variable to be set, cannot be **NULL**
|
||||
*
|
||||
* @return **true** is successful, **false** if otherwise. Upon success,
|
||||
* `width` contains valid data, otherwise, the
|
||||
* variable is unchanged.
|
||||
*/
|
||||
bool sprite_object_get_width(SpriteObject* sprite_object, int* width);
|
||||
|
||||
/**
|
||||
* @brief Get the `focused` variable from a SpriteObject
|
||||
*
|
||||
* @param sprite_object valid pointer to SpriteObject to check
|
||||
*
|
||||
* @return `true` if the SpriteObject is focused, `false` otherwise
|
||||
*/
|
||||
bool sprite_object_is_focused(SpriteObject* sprite_object);
|
||||
|
||||
/**
|
||||
* @brief Set sprite position. Inlined for efficiency
|
||||
*
|
||||
* @param sprite poitner to Sprite to adjust the position of. A **NULL** check is
|
||||
* not performed, though the value cannot be **NULL**.
|
||||
*
|
||||
* @param x horizontal position in pixels
|
||||
* @param y vertical position in pixels
|
||||
*/
|
||||
INLINE void sprite_position(Sprite* sprite, int x, int y)
|
||||
{
|
||||
sprite->pos.x = x;
|
||||
sprite->pos.y = y;
|
||||
|
|
@ -44,23 +334,4 @@ INLINE void sprite_position(Sprite *sprite, int x, int y)
|
|||
obj_set_pos(sprite->obj, x, y);
|
||||
}
|
||||
|
||||
// Sprite functions
|
||||
void sprite_init();
|
||||
void sprite_draw();
|
||||
int sprite_get_pb(const Sprite* sprite);
|
||||
|
||||
// SpriteObject methods
|
||||
SpriteObject *sprite_object_new();
|
||||
void sprite_object_destroy(SpriteObject **sprite_object);
|
||||
void sprite_object_set_sprite(SpriteObject* sprite_object, Sprite* sprite);
|
||||
void sprite_object_reset_transform(SpriteObject* sprite_object);
|
||||
void sprite_object_update(SpriteObject *sprite_object);
|
||||
void sprite_object_shake(SpriteObject* sprite_object, mm_word sound_id);
|
||||
|
||||
void sprite_object_set_selected(SpriteObject* sprite_object, bool selected);
|
||||
bool sprite_object_is_selected(SpriteObject* sprite_object);
|
||||
Sprite* sprite_object_get_sprite(SpriteObject* sprite_object);
|
||||
void sprite_object_set_focus(SpriteObject* sprite_object, bool focus);
|
||||
bool sprite_object_is_focused(SpriteObject* sprite_object);
|
||||
|
||||
#endif // SPRITE_H
|
||||
#endif // SPRITE_H
|
||||
|
|
|
|||
167
include/util.h
|
|
@ -1,44 +1,145 @@
|
|||
/**
|
||||
* @file util.h
|
||||
*
|
||||
* @brief Utilities relating around number string representation and protected arithmatic helper
|
||||
* functions
|
||||
*/
|
||||
#ifndef UTIL_H
|
||||
#define UTIL_H
|
||||
|
||||
static inline int get_digits(int n) // https://stackoverflow.com/questions/1068849/how-do-i-determine-the-number-of-digits-of-an-integer-in-c
|
||||
{
|
||||
if (n < 10) return 1;
|
||||
if (n < 100) return 2;
|
||||
if (n < 1000) return 3;
|
||||
if (n < 10000) return 4;
|
||||
if (n < 100000) return 5;
|
||||
if (n < 1000000) return 6;
|
||||
if (n < 10000000) return 7;
|
||||
if (n < 100000000) return 8;
|
||||
if (n < 1000000000) return 9;
|
||||
return 10;
|
||||
}
|
||||
#include <stdint.h>
|
||||
|
||||
static inline int get_digits_odd(int n)
|
||||
{
|
||||
if (n < 100) return 1;
|
||||
if (n < 10000) return 2;
|
||||
if (n < 1000000) return 3;
|
||||
if (n < 100000000) return 4;
|
||||
return 5;
|
||||
}
|
||||
|
||||
static inline int get_digits_even(int n)
|
||||
{
|
||||
if (n < 10) return 1;
|
||||
if (n < 1000) return 2;
|
||||
if (n < 100000) return 3;
|
||||
if (n < 10000000) return 4;
|
||||
return 5;
|
||||
}
|
||||
/**
|
||||
* @def GBAL_UNUSED
|
||||
* @brief A friendly wrapper around the not so friendly looking __attribute__ syntax for ((unused))
|
||||
*/
|
||||
#define GBAL_UNUSED __attribute__((unused))
|
||||
|
||||
#define UNDEFINED -1
|
||||
|
||||
/**
|
||||
* @def NUM_ELEM_IN_ARR
|
||||
* @brief Get the number of elements in an array
|
||||
*
|
||||
* @param arr input array
|
||||
*/
|
||||
#define NUM_ELEM_IN_ARR(arr) (sizeof(arr) / sizeof((arr)[0]))
|
||||
|
||||
#define INT_MAX_DIGITS 10 // strlen(str(INT_MAX)) = strlen("2147483647")
|
||||
#define INT_MAX_DIGITS 11 // strlen(str(INT_MAX)) = strlen("-2147483647")
|
||||
#define UINT_MAX_DIGITS 10 // strlen(str(UINT32_MAX)) = strlen("4294967295")
|
||||
#define UINT8_MAX_DIGITS 3 // strlen(str(UINT8_MAX)) = strlen("255")
|
||||
|
||||
int int_arr_max(int int_arr[], int size);
|
||||
#define ONE_K 1000
|
||||
#define ONE_M 1000000
|
||||
#define ONE_B 1000000000
|
||||
|
||||
#endif // UTIL_H
|
||||
#define ONE_K_ZEROS 3
|
||||
#define ONE_M_ZEROS 6
|
||||
#define ONE_B_ZEROS 9
|
||||
|
||||
// The suffix replaces everything past the third digit, e.g. "999K" -> "1M"
|
||||
// so it needs at least this number of chars to be able to display any suffixed number
|
||||
#define SUFFIXED_NUM_MIN_REQ_CHARS 4
|
||||
|
||||
/**
|
||||
* @brief Avoid overflow when adding two u32 integers
|
||||
*
|
||||
* @param a left operator **a + b**
|
||||
* @param b left operator **a + b**
|
||||
*
|
||||
* @return the result of **a + b** or **UINT32_MAX** in case of overflow
|
||||
*/
|
||||
uint32_t u32_protected_add(uint32_t a, uint32_t b);
|
||||
|
||||
/**
|
||||
* @brief Avoid overflow when adding two u16 integers
|
||||
*
|
||||
* @param a left operator **a + b**
|
||||
* @param b left operator **a + b**
|
||||
*
|
||||
* @return the result of **a + b** or **UINT16_MAX** in case of overflow
|
||||
*/
|
||||
uint16_t u16_protected_add(uint16_t a, uint16_t b);
|
||||
|
||||
/**
|
||||
* @brief Avoid overflow when multiplying two u32 integers
|
||||
*
|
||||
* @param a left operator **a * b**
|
||||
* @param b left operator **a * b**
|
||||
*
|
||||
* @return the result of **a * b** or **UINT32_MAX** in case of overflow
|
||||
*/
|
||||
uint32_t u32_protected_mult(uint32_t a, uint32_t b);
|
||||
|
||||
/**
|
||||
* @brief Avoid overflow when multiplying two u16 integers
|
||||
*
|
||||
* @param a left operator **a * b**
|
||||
* @param b left operator **a * b**
|
||||
*
|
||||
* @return the result of **a * b** or **UINT16_MAX** in case of overflow
|
||||
*/
|
||||
uint16_t u16_protected_mult(uint16_t a, uint16_t b);
|
||||
|
||||
/**
|
||||
* @brief Truncate an unsigned number into a suffixed string representation e.g. 12000 -> "12K"
|
||||
* The least significant digits are rounded down e.g. 12345 -> "12K", 12987 -> "12K"
|
||||
*
|
||||
* @param num The number to truncate, can be anything from 0 to UINT32_MAX.
|
||||
*
|
||||
* @param num_req_chars The number of characters to constrain the string to.
|
||||
* The function will use up as much characters as it can
|
||||
* in order to maintain as much accuracy as possible.
|
||||
* So numbers are not fully truncated if not necessary,
|
||||
* e.g. 123123000 -> "123123K" for example value 7,
|
||||
* and if num_req_chars > u32_get_digits(num) the number will not
|
||||
* be truncated at all.
|
||||
* Passing less than SUFFIXED_NUM_MIN_REQ_CHARS may result in an
|
||||
* output string longer than num_req_chars but
|
||||
* can be done to truncate 1000s -> "1K", 2000 -> "2K" etc.
|
||||
* which wouldn't be otherwise.
|
||||
*
|
||||
* @param out_str An output buffer to write the resulting string to.
|
||||
* Must be of size UINT_MAX_DIGITS + 1. + 1 for null-terminator.
|
||||
* At that size the suffix character will always be accounted for since
|
||||
* a number with more digits than UINT_MAX_DIGITS will not be handled nor
|
||||
* truncated.
|
||||
*/
|
||||
void truncate_uint_to_suffixed_str(
|
||||
uint32_t num,
|
||||
int num_req_chars,
|
||||
char out_str_buff[UINT_MAX_DIGITS + 1]
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Get the number of digits in a 32-bit unsigned number
|
||||
* https://stackoverflow.com/questions/1068849/how-do-i-determine-the-number-of-digits-of-an-integer-in-c
|
||||
*
|
||||
* @param n 32-bit unsigned value to find the number of decimal digits of
|
||||
*
|
||||
* @return the number of digits in a number
|
||||
*/
|
||||
static inline int u32_get_digits(uint32_t n)
|
||||
{
|
||||
if (n < 10)
|
||||
return 1;
|
||||
if (n < 100)
|
||||
return 2;
|
||||
if (n < 1000)
|
||||
return 3;
|
||||
if (n < 10000)
|
||||
return 4;
|
||||
if (n < 100000)
|
||||
return 5;
|
||||
if (n < 1000000)
|
||||
return 6;
|
||||
if (n < 10000000)
|
||||
return 7;
|
||||
if (n < 100000000)
|
||||
return 8;
|
||||
if (n < 1000000000)
|
||||
return 9;
|
||||
return 10;
|
||||
}
|
||||
|
||||
#endif // UTIL_H
|
||||
|
|
|
|||
116
scripts/generate_font.py
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from PIL import Image, ImageOps
|
||||
import argparse
|
||||
|
||||
CHAR_WIDTH = 8
|
||||
CHAR_HEIGHT = 8
|
||||
NUM_VERT_CHARS = 6
|
||||
NUM_HORZ_CHARS = 16
|
||||
WORD_SIZE = 32
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-i", "--input", required=True, help="input file")
|
||||
parser.add_argument("-o", "--output", required=True, help="output file")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
in_path = args.input
|
||||
out_path = args.output
|
||||
|
||||
# The font is stored in a 4-bit colormap png and is made of a grid of
|
||||
# 16x6 characters:
|
||||
# |-------------16---------------|
|
||||
# | [][][][][][][][][][][][][][][][]
|
||||
# | [][][][][][][][][][][][][][][][]
|
||||
# 6 [][][][][][][][][][][][][][][][]
|
||||
# | [][][][][][][][][][][][][][][][]
|
||||
# | [][][][][][][][][][][][][][][][]
|
||||
# | [][][][][][][][][][][][][][][][]
|
||||
#
|
||||
# Each character is a bitmap of 8x8 pixels.
|
||||
#
|
||||
# For example, the ! :
|
||||
#
|
||||
# ___00___
|
||||
# ___00___
|
||||
# ___00___
|
||||
# ___00___
|
||||
# ___00___
|
||||
# ___00___
|
||||
# ________
|
||||
# ___00___
|
||||
#
|
||||
# The font file is organized to go character-by-character and left-to-right
|
||||
|
||||
img = Image.open(in_path)
|
||||
img = img.convert("P")
|
||||
img = img.convert('1')
|
||||
img = ImageOps.invert(img)
|
||||
|
||||
glyphs = []
|
||||
for char_y in range(NUM_VERT_CHARS):
|
||||
for char_x in range(NUM_HORZ_CHARS):
|
||||
# Get the 8x8 crop area at our current character in the image
|
||||
pix_x = char_x * CHAR_WIDTH
|
||||
pix_y = char_y * CHAR_HEIGHT
|
||||
crop_area = (pix_x, pix_y, pix_x + CHAR_WIDTH, pix_y + CHAR_HEIGHT)
|
||||
|
||||
pixel_block_img = img.crop(crop_area)
|
||||
pixels = list(pixel_block_img.getdata())
|
||||
|
||||
word0 = 0
|
||||
word1 = 0
|
||||
for i, pixel in enumerate(pixels):
|
||||
bit = 1 if pixel > 0 else 0
|
||||
if i < WORD_SIZE:
|
||||
word0 = word0 | (bit << i)
|
||||
else:
|
||||
word1 = word1 | (bit << (i - WORD_SIZE))
|
||||
glyphs.append((word0, word1))
|
||||
|
||||
NUM_WORDS_IN_ROW = 8
|
||||
first = True
|
||||
|
||||
with open(out_path, "w") as out:
|
||||
out.write("""
|
||||
|
||||
@{{BLOCK(gbalatro_sys8)
|
||||
|
||||
.section .rodata
|
||||
.align 2
|
||||
.global gbalatro_sys8Font
|
||||
gbalatro_sys8Font:
|
||||
.word gbalatro_sys8Glyphs, 0, 0
|
||||
.hword 32, 96
|
||||
.byte 8, 8
|
||||
.byte 8, 8
|
||||
.hword 8
|
||||
.byte 1, 0
|
||||
|
||||
.section .rodata
|
||||
.align 2
|
||||
.global gbalatro_sys8Glyphs @ 768 bytes (192 unsigned ints)
|
||||
gbalatro_sys8Glyphs:
|
||||
""")
|
||||
|
||||
for i, glyph in enumerate(glyphs):
|
||||
if first:
|
||||
out.write(" .word ")
|
||||
first = False
|
||||
|
||||
out.write(f"0x{glyph[0]:08X},0x{glyph[1]:08X}")
|
||||
|
||||
# This checks to see if we have reached NUM_WORDS_IN_ROW words, (divided by
|
||||
# 2 because that's the number of entries in each char[])
|
||||
if (((i + 1) % (NUM_WORDS_IN_ROW // 2)) == 0):
|
||||
out.write("\n")
|
||||
if i != (len(glyphs) - 1):
|
||||
out.write(" .word ")
|
||||
else:
|
||||
out.write(",")
|
||||
|
||||
|
||||
out.write("\n")
|
||||
out.write("\n")
|
||||
out.write('@}}BLOCK(gbalatro_sys8)\n')
|
||||
27
scripts/get_hash.py
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
import re
|
||||
|
||||
def grep_binary_offsets(path, pattern):
|
||||
with open(path, "rb") as f:
|
||||
data = f.read()
|
||||
|
||||
results = []
|
||||
for match in re.finditer(rb"[ -~]{4,}", data):
|
||||
s = match.group()
|
||||
if re.search(pattern.encode(), s):
|
||||
results.append((match.start(), s.decode("utf-8", errors="ignore")))
|
||||
return results
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python get_hash.py <file>")
|
||||
sys.exit(1)
|
||||
|
||||
file_path = sys.argv[1]
|
||||
pattern = "GBALATRO_VERSION"
|
||||
|
||||
for offset, text in grep_binary_offsets(file_path, pattern):
|
||||
version = text.split(":")[1]
|
||||
print(f"git hash: {version}")
|
||||
77
scripts/get_memory_map.sh
Executable file
|
|
@ -0,0 +1,77 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ELF_FILE="${ELF_FILE-./build/balatro-gba.elf}"
|
||||
POOL_DEF_FILE="${POOL_DEF_FILE-./include/def_balatro_mempool.h}"
|
||||
READELF="${READELF-/opt/devkitpro/devkitARM/bin/arm-none-eabi-readelf}"
|
||||
TOTAL_BYTES=0
|
||||
|
||||
if [ ! -f "$POOL_DEF_FILE" ]; then
|
||||
echo "Mempool definition file not found: $POOL_DEF_FILE"
|
||||
echo "You can set your mempool definition file with:"
|
||||
echo " POOL_DEF_FILE=<mempool-def-file> $(basename $0)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$ELF_FILE" ]; then
|
||||
echo "elf file not found: $ELF_FILE"
|
||||
echo "You can set your elf file with:"
|
||||
echo " ELF_FILE=<elf-file> $(basename $0)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -x "$READELF" ]; then
|
||||
echo "ERROR: \"$READELF\" is not an executable file."
|
||||
echo "You can override the file location for 'arm-none-eabi-readelf' with the READELF env variable."
|
||||
echo " e.g. $ READELF=\"/my/custom/location/arm-none-eabi-readelf\" $(basename $0) <file>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
print_line_break() {
|
||||
echo "--------------------------------------------------------------------"
|
||||
}
|
||||
|
||||
get_pool_names() {
|
||||
grep POOL_ENTRY "$POOL_DEF_FILE" | sed -n 's@.*(\(.*\)).*@\1@p' | sed 's@,@@g' | cut -d ' ' -f 1
|
||||
}
|
||||
|
||||
print_line_break
|
||||
printf "%-16s| %-10s | %-10s | %-10s | %-10s \n" "Object" "address" "pool size" "func size" "bitmap size"
|
||||
print_line_break
|
||||
|
||||
for name in $(get_pool_names); do
|
||||
output_pool="$( \
|
||||
"$READELF" -sW "$ELF_FILE" | \
|
||||
grep "${name}_" | \
|
||||
grep OBJECT | \
|
||||
grep storage | \
|
||||
sed -E 's@ +@ @g; s@^ @@' \
|
||||
)"
|
||||
output_func="$( \
|
||||
"$READELF" -sW "$ELF_FILE" | \
|
||||
grep -E "pool_free|pool_get|pool_init" | \
|
||||
grep -E "${name}$" | \
|
||||
sed -E 's@ +@ @g; s@^ @@' | \
|
||||
tr -d '\n' \
|
||||
)"
|
||||
output_bitset="$( \
|
||||
"$READELF" -sW "$ELF_FILE" | \
|
||||
grep -E "${name}_bitset_w" | \
|
||||
grep OBJECT | \
|
||||
sed -E 's@ +@ @g; s@^ @@' | \
|
||||
tr -d '\n' \
|
||||
)"
|
||||
|
||||
address="$(cut -d ' ' -f 2 <<< $output_pool)"
|
||||
pool_size="$(cut -d ' ' -f 3 <<< $output_pool)"
|
||||
func_size="$(cut -d ' ' -f 3 <<< $output_func)"
|
||||
bitset_size="$(cut -d ' ' -f 3 <<< $output_bitset)"
|
||||
|
||||
TOTAL_BYTES=$(( TOTAL_BYTES + pool_size + func_size + bitset_size ))
|
||||
|
||||
printf "%-16s| 0x%8s | %-10u | %-10u | %-10u \n" "$name" "$address" "$pool_size" "$func_size" "$bitset_size"
|
||||
done
|
||||
|
||||
print_line_break
|
||||
echo Total bytes used: $TOTAL_BYTES
|
||||
|
|
@ -1,57 +1,28 @@
|
|||
#include "affine_background.h"
|
||||
|
||||
#include "affine_background_gfx.h"
|
||||
#include "affine_main_menu_background_gfx.h"
|
||||
|
||||
#include "graphic_utils.h"
|
||||
|
||||
#define ANIMATION_SPEED_DIVISOR 16
|
||||
|
||||
BG_AFFINE bgaff_arr[SCREEN_HEIGHT + 1];
|
||||
// Prepare screen during VBLANK
|
||||
// Pre-computes the affine matrices values for each scanline and stores in bgaff_arr. This is to be
|
||||
// done in VBLANK so the HBLANK code can just fetch the values quickly.
|
||||
static IWRAM_CODE void s_affine_background_prep_bgaff_arr();
|
||||
|
||||
AFF_SRC_EX asx = {0};
|
||||
|
||||
enum AffineBackgroundID background = AFFINE_BG_MAIN_MENU;
|
||||
|
||||
static uint timer = 0;
|
||||
static BG_AFFINE _bgaff_arr[SCREEN_HEIGHT + 1];
|
||||
static AFF_SRC_EX _asx = {0};
|
||||
static enum AffineBackgroundID _background = AFFINE_BG_MAIN_MENU;
|
||||
static uint _timer = 0;
|
||||
|
||||
void affine_background_init()
|
||||
{
|
||||
{
|
||||
affine_background_update();
|
||||
|
||||
REG_BG_AFFINE[AFFINE_BG_IDX] = bg_aff_default;
|
||||
}
|
||||
|
||||
// Pre-computes the affine matrices values for each scanline
|
||||
// and stores in bgaff_arr.
|
||||
// This is to be done in VBLANK so the HBLANK code
|
||||
// can just fetch the values quickly.
|
||||
IWRAM_CODE void affine_background_prep_bgaff_arr()
|
||||
{
|
||||
for (u16 vcount = 0; vcount < SCREEN_HEIGHT; vcount++)
|
||||
{
|
||||
const s32 timer_s32 = timer << 8;
|
||||
const s32 vcount_s32 = vcount << 8;
|
||||
const s16 vcount_s16 = vcount;
|
||||
const s32 vcount_sine = lu_sin(vcount_s32 + timer_s32 / ANIMATION_SPEED_DIVISOR); // dividing the timer by 16 to make the animation slower
|
||||
|
||||
asx.scr_x = (SCREEN_WIDTH / 2); // 128 on x and y is an offset used to center the rotation
|
||||
asx.scr_y = vcount_s16 - (SCREEN_HEIGHT / 2); // scr_y must equal vcount otherwise the background will have no vertical difference
|
||||
asx.tex_x = (1000 * 1000) + (vcount_sine);
|
||||
asx.tex_y = (1000 * 1000);
|
||||
asx.sx = 128;
|
||||
asx.sy = 128;
|
||||
asx.alpha = vcount_sine + (timer_s32 / ANIMATION_SPEED_DIVISOR);
|
||||
|
||||
bg_rotscale_ex(&bgaff_arr[vcount], &asx);
|
||||
}
|
||||
|
||||
/* HBLANK occurs after the scanline so REG_VCOUNT represents the
|
||||
* the scanline that just passed, so when it's SCREEN_HEIGHT we will
|
||||
* actually be updating the first line
|
||||
*/
|
||||
bgaff_arr[SCREEN_HEIGHT] = bgaff_arr[0];
|
||||
}
|
||||
|
||||
IWRAM_CODE void affine_background_hblank()
|
||||
{
|
||||
vu16 vcount = REG_VCOUNT;
|
||||
|
|
@ -62,69 +33,110 @@ IWRAM_CODE void affine_background_hblank()
|
|||
}
|
||||
|
||||
// See comment in affine_background_prep_bgaff_arr()
|
||||
REG_BG_AFFINE[AFFINE_BG_IDX] = bgaff_arr[vcount + 1];
|
||||
REG_BG_AFFINE[AFFINE_BG_IDX] = _bgaff_arr[vcount + 1];
|
||||
}
|
||||
|
||||
void affine_background_update()
|
||||
{
|
||||
if (REG_IE & IRQ_HBLANK) // High quality mode with HBLANK interrupt
|
||||
{
|
||||
affine_background_prep_bgaff_arr();
|
||||
s_affine_background_prep_bgaff_arr();
|
||||
}
|
||||
else // Low quality mode without HBLANK interrupt
|
||||
{
|
||||
asx.scr_x = 0;
|
||||
asx.scr_y = 0;
|
||||
asx.tex_x += 5;
|
||||
asx.tex_y += 12;
|
||||
asx.sx = ((lu_sin(timer * 100)) >> 8) + 256; // Scale the sine value to fit in a s16
|
||||
asx.sy = ((lu_sin(timer * 100 + 0x4000)) >> 8) + 256; // Scale the sine value to fit in a s16
|
||||
asx.alpha = 0;
|
||||
_asx.scr_x = 0;
|
||||
_asx.scr_y = 0;
|
||||
_asx.tex_x += 5;
|
||||
_asx.tex_y += 12;
|
||||
// Scale the sine value to fit in a s16
|
||||
_asx.sx = ((lu_sin(_timer * 100)) >> 8) + 256;
|
||||
// Scale the sine value to fit in a s16
|
||||
_asx.sy = ((lu_sin(_timer * 100 + 0x4000)) >> 8) + 256;
|
||||
_asx.alpha = 0;
|
||||
|
||||
bg_rotscale_ex(&bgaff_arr[0], &asx);
|
||||
REG_BG_AFFINE[AFFINE_BG_IDX] = bgaff_arr[0];
|
||||
bg_rotscale_ex(&_bgaff_arr[0], &_asx);
|
||||
REG_BG_AFFINE[AFFINE_BG_IDX] = _bgaff_arr[0];
|
||||
}
|
||||
|
||||
timer++;
|
||||
_timer++;
|
||||
}
|
||||
|
||||
void affine_background_set_color(COLOR color)
|
||||
{
|
||||
affine_background_change_background(background); // Reload the palette to reset any previous color scaling
|
||||
// Reload the palette to reset any previous color scaling
|
||||
affine_background_change_background(_background);
|
||||
for (int i = 0; i < AFFINE_BG_PAL_LEN; i++)
|
||||
{
|
||||
clr_rgbscale(&pal_bg_mem[AFFINE_BG_PB] + i, &pal_bg_mem[AFFINE_BG_PB] + i, 1, color);
|
||||
}
|
||||
}
|
||||
|
||||
void affine_background_load_palette(const u16 *src)
|
||||
void affine_background_load_palette(const u16* src)
|
||||
{
|
||||
memcpy16(&pal_bg_mem[AFFINE_BG_PB], src, AFFINE_BG_PAL_LEN);
|
||||
}
|
||||
|
||||
void affine_background_change_background(enum AffineBackgroundID new_bg)
|
||||
{
|
||||
background = new_bg;
|
||||
_background = new_bg;
|
||||
|
||||
switch (background)
|
||||
switch (_background)
|
||||
{
|
||||
case AFFINE_BG_MAIN_MENU:
|
||||
REG_BG2CNT &= ~BG_AFF_32x32;
|
||||
REG_BG2CNT |= BG_AFF_16x16;
|
||||
REG_IE |= IRQ_HBLANK; // Enable HBLANK
|
||||
case AFFINE_BG_MAIN_MENU:
|
||||
REG_BG2CNT &= ~BG_AFF_32x32;
|
||||
REG_BG2CNT |= BG_AFF_16x16;
|
||||
REG_IE |= IRQ_HBLANK; // Enable HBLANK
|
||||
|
||||
memcpy32_tile8_with_palette_offset((u32*)&tile8_mem[AFFINE_BG_CBB], (const u32*)affine_main_menu_background_gfxTiles, affine_main_menu_background_gfxTilesLen/4, AFFINE_BG_PB);
|
||||
GRIT_CPY(&se_mem[AFFINE_BG_SBB], affine_main_menu_background_gfxMap);
|
||||
affine_background_load_palette(affine_main_menu_background_gfxPal);
|
||||
break;
|
||||
case AFFINE_BG_GAME:
|
||||
REG_BG2CNT &= ~BG_AFF_16x16;
|
||||
REG_BG2CNT |= BG_AFF_32x32;
|
||||
REG_IE &= ~IRQ_HBLANK; // Disable HBLANK
|
||||
memcpy32_tile8_with_palette_offset(
|
||||
(u32*)&tile8_mem[AFFINE_BG_CBB],
|
||||
(const u32*)affine_main_menu_background_gfxTiles,
|
||||
affine_main_menu_background_gfxTilesLen / 4,
|
||||
AFFINE_BG_PB
|
||||
);
|
||||
GRIT_CPY(&se_mem[AFFINE_BG_SBB], affine_main_menu_background_gfxMap);
|
||||
affine_background_load_palette(affine_main_menu_background_gfxPal);
|
||||
break;
|
||||
case AFFINE_BG_GAME:
|
||||
REG_BG2CNT &= ~BG_AFF_16x16;
|
||||
REG_BG2CNT |= BG_AFF_32x32;
|
||||
REG_IE &= ~IRQ_HBLANK; // Disable HBLANK
|
||||
|
||||
memcpy32_tile8_with_palette_offset((u32*)&tile8_mem[AFFINE_BG_CBB], (const u32*)affine_background_gfxTiles, affine_background_gfxTilesLen/4, AFFINE_BG_PB);
|
||||
GRIT_CPY(&se_mem[AFFINE_BG_SBB], affine_background_gfxMap);
|
||||
affine_background_load_palette(affine_background_gfxPal);
|
||||
break;
|
||||
memcpy32_tile8_with_palette_offset(
|
||||
(u32*)&tile8_mem[AFFINE_BG_CBB],
|
||||
(const u32*)affine_background_gfxTiles,
|
||||
affine_background_gfxTilesLen / 4,
|
||||
AFFINE_BG_PB
|
||||
);
|
||||
GRIT_CPY(&se_mem[AFFINE_BG_SBB], affine_background_gfxMap);
|
||||
affine_background_load_palette(affine_background_gfxPal);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static IWRAM_CODE void s_affine_background_prep_bgaff_arr()
|
||||
{
|
||||
for (u16 vcount = 0; vcount < SCREEN_HEIGHT; vcount++)
|
||||
{
|
||||
const s32 timer_s32 = _timer << 8;
|
||||
const s32 vcount_s32 = vcount << 8;
|
||||
const s16 vcount_s16 = vcount;
|
||||
const s32 vcount_sine = lu_sin(vcount_s32 + timer_s32 / ANIMATION_SPEED_DIVISOR);
|
||||
|
||||
_asx.scr_x = (SCREEN_WIDTH / 2);
|
||||
// scr_y must equal vcount otherwise the background will have no vertical difference
|
||||
_asx.scr_y = vcount_s16 - (SCREEN_HEIGHT / 2);
|
||||
_asx.tex_x = (1000 * 1000) + (vcount_sine);
|
||||
_asx.tex_y = (1000 * 1000);
|
||||
_asx.sx = 128;
|
||||
_asx.sy = 128;
|
||||
_asx.alpha = vcount_sine + (timer_s32 / ANIMATION_SPEED_DIVISOR);
|
||||
|
||||
bg_rotscale_ex(&_bgaff_arr[vcount], &_asx);
|
||||
}
|
||||
|
||||
/* HBLANK occurs after the scanline so REG_VCOUNT represents the
|
||||
* the scanline that just passed, so when it's SCREEN_HEIGHT we will
|
||||
* actually be updating the first line
|
||||
*/
|
||||
_bgaff_arr[SCREEN_HEIGHT] = _bgaff_arr[0];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,15 @@
|
|||
#include "audio_utils.h"
|
||||
#include <maxmod.h>
|
||||
|
||||
void play_sfx(mm_word id, mm_word rate)
|
||||
{
|
||||
mm_sound_effect sfx = { {id}, rate, 0, SFX_DEFAULT_VOLUME, SFX_DEFAULT_PAN, };
|
||||
mmEffectEx(&sfx);
|
||||
}
|
||||
#include "audio_utils.h"
|
||||
|
||||
#include <maxmod.h>
|
||||
|
||||
void play_sfx(mm_word id, mm_word rate, mm_byte volume)
|
||||
{
|
||||
mm_sound_effect sfx = {
|
||||
{id},
|
||||
rate,
|
||||
0,
|
||||
volume,
|
||||
SFX_DEFAULT_PAN,
|
||||
};
|
||||
mmEffectEx(&sfx);
|
||||
}
|
||||
|
|
|
|||
171
source/bitset.c
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
#include "bitset.h"
|
||||
|
||||
#include "util.h"
|
||||
|
||||
void bitset_set_idx(Bitset* bitset, int idx, bool on)
|
||||
{
|
||||
uint32_t i = idx / BITSET_BITS_PER_WORD;
|
||||
uint32_t b = idx % BITSET_BITS_PER_WORD;
|
||||
|
||||
// Below are the "fast" forms of the above operations, respectively.
|
||||
// These are more efficient, but removed for readability
|
||||
// See: https://github.com/cellos51/balatro-gba/pull/132#discussion_r2365966071
|
||||
// Divide by 32 to get the word index
|
||||
// uint32_t i = idx >> 5;
|
||||
// Get last 5-bits, same as a modulo (% 32) operation on positive numbers
|
||||
// uint32_t b = idx & 0x1F;
|
||||
if (on)
|
||||
{
|
||||
bitset->w[i] |= (uint32_t)1 << b;
|
||||
}
|
||||
else
|
||||
{
|
||||
bitset->w[i] &= ~((uint32_t)1 << b);
|
||||
}
|
||||
}
|
||||
|
||||
int bitset_set_next_free_idx(Bitset* bitset)
|
||||
{
|
||||
for (uint32_t i = 0; i < bitset->nwords; i++)
|
||||
{
|
||||
uint32_t inv = ~bitset->w[i];
|
||||
|
||||
// guard so we don't call `ctz` with 0, since __builtin_ctz(0) is undefined
|
||||
// https://gcc.gnu.org/onlinedocs/gcc/Bit-Operation-Builtins.html#index-_005f_005fbuiltin_005fctz
|
||||
//
|
||||
// By using the bitwise inverse of the word, you can skip words that are full
|
||||
// quickly (where the value is 0 or 'false' since all bits are '1', or 'in use'). Any value
|
||||
// greater than 0 indicates there is a free slot. Then, when counting the trailing 0's, you
|
||||
// can test very quickly where the first free slot is. This operation prevents looping
|
||||
// through every bit of filled flags, and will instead operate only on the first word with
|
||||
// free slots.
|
||||
if (inv)
|
||||
{
|
||||
int bit = __builtin_ctz(inv);
|
||||
bitset->w[i] |= ((uint32_t)1 << bit);
|
||||
int idx = i * BITSET_BITS_PER_WORD + bit;
|
||||
return (idx < bitset->cap) ? idx : UNDEFINED;
|
||||
}
|
||||
}
|
||||
|
||||
return UNDEFINED;
|
||||
}
|
||||
|
||||
void bitset_clear(Bitset* bitset)
|
||||
{
|
||||
for (int i = 0; i < bitset->nwords; i++)
|
||||
{
|
||||
bitset->w[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool bitset_is_empty(Bitset* bitset)
|
||||
{
|
||||
for (int i = 0; i < bitset->nwords; i++)
|
||||
{
|
||||
if (bitset->w[i])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool bitset_get_idx(Bitset* bitset, int idx)
|
||||
{
|
||||
uint32_t i = idx / BITSET_BITS_PER_WORD;
|
||||
uint32_t b = idx % BITSET_BITS_PER_WORD;
|
||||
|
||||
return bitset->w[i] & (uint32_t)1 << b;
|
||||
}
|
||||
|
||||
int bitset_num_set_bits(Bitset* bitset)
|
||||
{
|
||||
int sum = 0;
|
||||
|
||||
for (int i = 0; i < bitset->nwords; i++)
|
||||
{
|
||||
sum += __builtin_popcount(bitset->w[i]);
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
int bitset_find_idx_of_nth_set(const Bitset* bitset, int n)
|
||||
{
|
||||
int tracker = 0;
|
||||
int prev_tracker = 0;
|
||||
|
||||
for (int i = 0; i < bitset->nwords; i++)
|
||||
{
|
||||
tracker += __builtin_popcount(bitset->w[i]);
|
||||
|
||||
if (tracker > n)
|
||||
{
|
||||
// The index is here somewhere
|
||||
// this one is to count the 1's not the offset, underflow to -1 is good for finding the
|
||||
// 0 index
|
||||
int base = prev_tracker - 1;
|
||||
// this one is for the actual offset we want to map the id to
|
||||
int offset = bitset->nbits * i;
|
||||
for (int j = 0; j < bitset->nbits; j++)
|
||||
{
|
||||
if (base == n)
|
||||
{
|
||||
return offset - 1;
|
||||
}
|
||||
base += (bitset->w[i] >> j) & 0x01;
|
||||
offset++;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
prev_tracker = tracker;
|
||||
}
|
||||
|
||||
return UNDEFINED;
|
||||
}
|
||||
|
||||
BitsetItr bitset_itr_create(const Bitset* bitset)
|
||||
{
|
||||
BitsetItr itr = {
|
||||
.bitset = bitset,
|
||||
.word = 0,
|
||||
.bit = 0,
|
||||
.itr = 0,
|
||||
};
|
||||
|
||||
return itr;
|
||||
}
|
||||
|
||||
int bitset_itr_next(BitsetItr* itr)
|
||||
{
|
||||
// So, worst case scenario for this is one bit at the end of the last
|
||||
// word in the bitset. You would look (32 * 7) + 31 times!
|
||||
// This can be sped up with by checking if the word is empty first.
|
||||
// Then the worst enemy of this method would be something like a set bit at the end
|
||||
// of every word. In that case you would need to loop 31 times maximum.
|
||||
// So one last thing you could do is something like `bitset_allocate_idx` does with the
|
||||
// __builtin_ctz function as well.
|
||||
//
|
||||
// The point being, this can be very slow, but it's simple and can be much faster.
|
||||
for (; itr->word < itr->bitset->nwords; itr->word++)
|
||||
{
|
||||
for (; itr->bit < itr->bitset->nbits; itr->bit++)
|
||||
{
|
||||
itr->itr++;
|
||||
if (itr->bitset->w[itr->word] & (1 << itr->bit))
|
||||
{
|
||||
// if itr->bit == nbits on the next run, the for loop will handle it
|
||||
itr->bit++;
|
||||
// above we always make it one more than it is
|
||||
// it's so we can return without mutating the actual iterator
|
||||
// once it gets here. Just subtract one
|
||||
return itr->itr - 1;
|
||||
}
|
||||
}
|
||||
itr->bit = 0;
|
||||
}
|
||||
itr->word = 0;
|
||||
|
||||
return UNDEFINED;
|
||||
}
|
||||
159
source/blind.c
|
|
@ -1,95 +1,118 @@
|
|||
#include "blind.h"
|
||||
|
||||
#include "big_blind_gfx.h"
|
||||
#include "boss_blind_gfx.h"
|
||||
#include "graphic_utils.h"
|
||||
#include "small_blind_gfx.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <tonc.h>
|
||||
|
||||
#include "blind.h"
|
||||
#include "blinds_gfx.h"
|
||||
#include "graphic_utils.h"
|
||||
// Maps the ante number to the base blind requirement for that ante.
|
||||
// The game starts at ante 1 which is at index 1 for base requirement 300.
|
||||
// Ante 0 is also there in case it is ever reached.
|
||||
static const u32 ante_lut[] = {100, 300, 800, 2000, 5000, 11000, 20000, 35000, 50000};
|
||||
|
||||
// Palettes for the blinds (Transparency, Text Color, Shadow, Highlight, Main Color) Use this: http://www.budmelvin.com/dev/15bitconverter.html
|
||||
static const u16 small_blind_token_palette[PAL_ROW_LEN] = {0x0000, 0x7FFF, 0x34A1, 0x5DCB, 0x5104, 0x55A0, 0x2D01, 0x34E0};
|
||||
static const u16 big_blind_token_palette[PAL_ROW_LEN] = {0x0000, 0x2527, 0x15F5, 0x36FC, 0x1E9C, 0x01B4, 0x0D0A, 0x010E};
|
||||
static const u16 boss_blind_token_palette[PAL_ROW_LEN] = {0x0000, 0x2CC9, 0x3D0D, 0x5E14, 0x5171, 0x4D0F, 0x2CC8, 0x3089}; // This variable is temporary, each boss blind will have its own unique palette
|
||||
// Palettes for the blinds (Transparency, Text Color, Shadow, Highlight, Main Color) Use this:
|
||||
// http://www.budmelvin.com/dev/15bitconverter.html
|
||||
static const u16 small_blind_token_palette[PAL_ROW_LEN] =
|
||||
{0x0000, 0x7FFF, 0x34A1, 0x5DCB, 0x5104, 0x55A0, 0x2D01, 0x34E0};
|
||||
static const u16 big_blind_token_palette[PAL_ROW_LEN] =
|
||||
{0x0000, 0x2527, 0x15F5, 0x36FC, 0x1E9C, 0x01B4, 0x0D0A, 0x010E};
|
||||
// This variable is temporary, each boss blind will have its own unique palette
|
||||
static const u16 boss_blind_token_palette[PAL_ROW_LEN] =
|
||||
{0x0000, 0x2CC9, 0x3D0D, 0x5E14, 0x5171, 0x4D0F, 0x2CC8, 0x3089};
|
||||
|
||||
// clang-format off
|
||||
static Blind _blind_type_map[BLIND_TYPE_MAX] = {
|
||||
#define BLIND_INFO(NAME, name, multi, _reward) \
|
||||
{ \
|
||||
.type = BLIND_TYPE_##NAME, \
|
||||
.gfx_info = \
|
||||
{ \
|
||||
.tiles = name##_blind_gfxTiles, \
|
||||
.palette = name##_blind_token_palette, \
|
||||
.tid = NAME##_BLIND_TID, \
|
||||
.pb = NAME##_BLIND_PB, \
|
||||
}, \
|
||||
.score_req_multipler = multi, \
|
||||
.reward = _reward, \
|
||||
},
|
||||
BLIND_TYPE_INFO_TABLE
|
||||
#undef BLIND_INFO
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
static void s_blind_gfx_init(enum BlindType type);
|
||||
|
||||
GBAL_UNUSED
|
||||
void blind_set_boss_graphics(const unsigned int* tiles, const u16* palette)
|
||||
{
|
||||
// TODO: This function is unused and not fully fleshed out.
|
||||
// We need to support more boss blind graphics in the future.
|
||||
// The idea here is that we can call this function to set the boss up to
|
||||
// render new tiles
|
||||
//
|
||||
// This will eventually be in it's own map mapping graphic data to
|
||||
// boss types.
|
||||
|
||||
_blind_type_map[BLIND_TYPE_BOSS].gfx_info.tiles = tiles;
|
||||
_blind_type_map[BLIND_TYPE_BOSS].gfx_info.palette = palette;
|
||||
s_blind_gfx_init(BLIND_TYPE_BOSS);
|
||||
}
|
||||
|
||||
void blind_init()
|
||||
{
|
||||
// Blind graphics (fighting grit every step of the way as usual)
|
||||
GRIT_CPY(&tile_mem[4][SMALL_BLIND_TID], blinds_gfxTiles);
|
||||
for (int i = 0; i < BLIND_TYPE_MAX; i++)
|
||||
{
|
||||
s_blind_gfx_init(i);
|
||||
}
|
||||
|
||||
memcpy16(&pal_obj_mem[PAL_ROW_LEN * SMALL_BLIND_PB], &small_blind_token_palette, sizeof(small_blind_token_palette) / 2);
|
||||
memcpy16(&pal_obj_mem[PAL_ROW_LEN * BIG_BLIND_PB], &big_blind_token_palette, sizeof(big_blind_token_palette) / 2);
|
||||
// Boss Blind (This is temporary. Each boss blind is unique and will have to have its own graphics and palette which will probably be stored in some huge array)
|
||||
memcpy16(&pal_obj_mem[PAL_ROW_LEN * BOSS_BLIND_PB], &boss_blind_token_palette, sizeof(boss_blind_token_palette) / 2);
|
||||
return;
|
||||
}
|
||||
|
||||
int blind_get_requirement(int type, int ante)
|
||||
u32 blind_get_requirement(enum BlindType type, int ante)
|
||||
{
|
||||
// Ensure ante is within valid range
|
||||
if (ante < 0 || ante > MAX_ANTE)
|
||||
{
|
||||
ante = 0; // Ensure ante is within valid range
|
||||
}
|
||||
ante = 0;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case SMALL_BLIND:
|
||||
return ante_lut[ante];
|
||||
case BIG_BLIND:
|
||||
return (ante_lut[ante] * 3) / 2; // X1.5
|
||||
case BOSS_BLIND:
|
||||
return ante_lut[ante] * 2; // X2
|
||||
default:
|
||||
return 0; // Invalid type
|
||||
}
|
||||
return fx2int(_blind_type_map[type].score_req_multipler * ante_lut[ante]);
|
||||
}
|
||||
|
||||
int blind_get_reward(int type)
|
||||
int blind_get_reward(enum BlindType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case SMALL_BLIND:
|
||||
return 3;
|
||||
case BIG_BLIND:
|
||||
return 4;
|
||||
case BOSS_BLIND:
|
||||
return 5;
|
||||
default:
|
||||
return 0; // Invalid type
|
||||
}
|
||||
return _blind_type_map[type].reward;
|
||||
}
|
||||
|
||||
u16 blind_get_color(int type, int index)
|
||||
u16 blind_get_color(enum BlindType type, enum BlindColorIndex index)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case SMALL_BLIND:
|
||||
return small_blind_token_palette[index];
|
||||
case BIG_BLIND:
|
||||
return big_blind_token_palette[index];
|
||||
case BOSS_BLIND:
|
||||
return boss_blind_token_palette[index];
|
||||
default:
|
||||
return 0; // Invalid type
|
||||
}
|
||||
return _blind_type_map[type].gfx_info.palette[index];
|
||||
}
|
||||
|
||||
Sprite *blind_token_new(int type, int x, int y, int sprite_index)
|
||||
Sprite* blind_token_new(enum BlindType type, int x, int y, int sprite_index)
|
||||
{
|
||||
Sprite *sprite = NULL;
|
||||
u16 a0 = ATTR0_SQUARE | ATTR0_4BPP;
|
||||
u16 a1 = ATTR1_SIZE_32x32;
|
||||
u32 tid = _blind_type_map[type].gfx_info.tid, pb = _blind_type_map[type].gfx_info.pb;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case SMALL_BLIND:
|
||||
sprite = sprite_new(ATTR0_SQUARE | ATTR0_4BPP, ATTR1_SIZE_32x32, SMALL_BLIND_TID, SMALL_BLIND_PB, sprite_index);
|
||||
break;
|
||||
case BIG_BLIND:
|
||||
sprite = sprite_new(ATTR0_SQUARE | ATTR0_4BPP, ATTR1_SIZE_32x32, BIG_BLIND_TID, BIG_BLIND_PB, sprite_index);
|
||||
break;
|
||||
case BOSS_BLIND:
|
||||
sprite = sprite_new(ATTR0_SQUARE | ATTR0_4BPP, ATTR1_SIZE_32x32, BOSS_BLIND_TID, BOSS_BLIND_PB, sprite_index);
|
||||
break;
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
Sprite* sprite = sprite_new(a0, a1, tid, pb, sprite_index);
|
||||
|
||||
sprite_position(sprite, x, y);
|
||||
|
||||
return sprite;
|
||||
}
|
||||
|
||||
static void s_blind_gfx_init(enum BlindType type)
|
||||
{
|
||||
// TODO: Re-add grit copy. You need to decouple the blind graphics first.
|
||||
// This will allow this function to change the boss graphics info
|
||||
// GRIT_CPY(&tile_mem[TILE_MEM_OBJ_CHARBLOCK0_IDX][_blind_type_map[type].pal_info.tid], tiles);
|
||||
BlindGfxInfo* p_gfx = &_blind_type_map[type].gfx_info;
|
||||
memcpy32(
|
||||
&tile_mem[TILE_MEM_OBJ_CHARBLOCK0_IDX][p_gfx->tid],
|
||||
p_gfx->tiles,
|
||||
BLIND_SPRITE_COPY_SIZE
|
||||
);
|
||||
memcpy16(&pal_obj_bank[p_gfx->pb], p_gfx->palette, PAL_ROW_LEN);
|
||||
}
|
||||
|
|
|
|||
29
source/button.c
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#include "button.h"
|
||||
|
||||
#include "audio_utils.h"
|
||||
#include "soundbank.h"
|
||||
|
||||
#include <tonc.h>
|
||||
|
||||
void button_set_highlight(Button* button, bool highlight)
|
||||
{
|
||||
if (button == NULL)
|
||||
return;
|
||||
|
||||
u16 set_color = highlight ? BTN_HIGHLIGHT_COLOR : pal_bg_mem[button->button_pal_idx];
|
||||
|
||||
memset16(&pal_bg_mem[button->border_pal_idx], set_color, 1);
|
||||
}
|
||||
|
||||
void button_press(Button* button)
|
||||
{
|
||||
if (button == NULL || button->on_pressed == NULL ||
|
||||
(button->can_be_pressed != NULL && !button->can_be_pressed()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
play_sfx(SFX_BUTTON, MM_BASE_PITCH_RATE, BUTTON_SFX_VOLUME);
|
||||
|
||||
button->on_pressed();
|
||||
}
|
||||
|
|
@ -1,17 +1,19 @@
|
|||
#include "card.h"
|
||||
|
||||
#include <maxmod.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "deck_gfx.h"
|
||||
#include "graphic_utils.h"
|
||||
|
||||
#include <maxmod.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// Audio
|
||||
#include "pool.h"
|
||||
#include "soundbank.h"
|
||||
|
||||
// Card sprites lookup table. First index is the suit, second index is the rank. The value is the tile index.
|
||||
const static u16 card_sprite_lut[NUM_SUITS][NUM_RANKS] = {
|
||||
{0, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192},
|
||||
// Card sprites lookup table. First index is the suit, second index is the rank. The value is the
|
||||
// tile index.
|
||||
const static u16 _card_sprite_lut[NUM_SUITS][NUM_RANKS] = {
|
||||
{0, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192},
|
||||
{208, 224, 240, 256, 272, 288, 304, 320, 336, 352, 368, 384, 400},
|
||||
{416, 432, 448, 464, 480, 496, 512, 528, 544, 560, 576, 592, 608},
|
||||
{624, 640, 656, 672, 688, 704, 720, 736, 752, 768, 784, 800, 816}
|
||||
|
|
@ -23,9 +25,9 @@ void card_init()
|
|||
}
|
||||
|
||||
// Card methods
|
||||
Card *card_new(u8 suit, u8 rank)
|
||||
Card* card_new(u8 suit, u8 rank)
|
||||
{
|
||||
Card *card = malloc(sizeof(Card));
|
||||
Card* card = POOL_GET(Card);
|
||||
|
||||
card->suit = suit;
|
||||
card->rank = rank;
|
||||
|
|
@ -33,14 +35,13 @@ Card *card_new(u8 suit, u8 rank)
|
|||
return card;
|
||||
}
|
||||
|
||||
void card_destroy(Card **card)
|
||||
void card_destroy(Card** card)
|
||||
{
|
||||
if (*card == NULL) return;
|
||||
free(*card);
|
||||
POOL_FREE(Card, *card);
|
||||
*card = NULL;
|
||||
}
|
||||
|
||||
u8 card_get_value(Card *card)
|
||||
u8 card_get_value(Card* card)
|
||||
{
|
||||
if (card->rank == JACK || card->rank == QUEEN || card->rank == KING)
|
||||
{
|
||||
|
|
@ -59,36 +60,50 @@ u8 card_get_value(Card *card)
|
|||
}
|
||||
|
||||
// CardObject methods
|
||||
CardObject *card_object_new(Card *card)
|
||||
CardObject* card_object_new(Card* card)
|
||||
{
|
||||
CardObject *card_object = malloc(sizeof(CardObject));
|
||||
CardObject* card_object = POOL_GET(CardObject);
|
||||
|
||||
card_object->card = card;
|
||||
card_object->sprite_object = sprite_object_new();
|
||||
card_object->selected = false;
|
||||
|
||||
return card_object;
|
||||
}
|
||||
|
||||
void card_object_destroy(CardObject **card_object)
|
||||
void card_object_destroy(CardObject** card_object)
|
||||
{
|
||||
if (*card_object == NULL) return;
|
||||
if (*card_object == NULL)
|
||||
return;
|
||||
sprite_object_destroy(&((*card_object)->sprite_object));
|
||||
//card_destroy(&(*card_object)->card); // In practice, this is unnecessary because the card will be inserted into the discard pile and then back into the deck. If you need to destroy the card, you can do it manually before calling this function.
|
||||
free(*card_object);
|
||||
POOL_FREE(CardObject, *card_object);
|
||||
*card_object = NULL;
|
||||
}
|
||||
|
||||
void card_object_update(CardObject* card_object)
|
||||
{
|
||||
if (card_object == NULL) return;
|
||||
if (card_object == NULL)
|
||||
return;
|
||||
sprite_object_update(card_object->sprite_object);
|
||||
}
|
||||
|
||||
void card_object_set_sprite(CardObject *card_object, int layer)
|
||||
void card_object_set_sprite(CardObject* card_object, int layer)
|
||||
{
|
||||
int tile_index = CARD_TID + (layer * CARD_SPRITE_OFFSET);
|
||||
memcpy32(&tile_mem[4][tile_index], &deck_gfxTiles[card_sprite_lut[card_object->card->suit][card_object->card->rank] * TILE_SIZE], TILE_SIZE * CARD_SPRITE_OFFSET);
|
||||
sprite_object_set_sprite(card_object->sprite_object, sprite_new(ATTR0_SQUARE | ATTR0_4BPP | ATTR0_AFF, ATTR1_SIZE_32, tile_index, 0, layer + CARD_STARTING_LAYER));
|
||||
memcpy32(
|
||||
&tile_mem[TILE_MEM_OBJ_CHARBLOCK0_IDX][tile_index],
|
||||
&deck_gfxTiles
|
||||
[_card_sprite_lut[card_object->card->suit][card_object->card->rank] * TILE_SIZE],
|
||||
TILE_SIZE * CARD_SPRITE_OFFSET
|
||||
);
|
||||
Sprite* sprite = sprite_new(
|
||||
ATTR0_SQUARE | ATTR0_4BPP | ATTR0_AFF,
|
||||
ATTR1_SIZE_32,
|
||||
tile_index,
|
||||
0,
|
||||
layer + CARD_STARTING_LAYER
|
||||
);
|
||||
sprite_object_set_sprite(card_object->sprite_object, sprite);
|
||||
}
|
||||
|
||||
void card_object_shake(CardObject* card_object, mm_word sound_id)
|
||||
|
|
@ -100,14 +115,14 @@ void card_object_set_selected(CardObject* card_object, bool selected)
|
|||
{
|
||||
if (card_object == NULL)
|
||||
return;
|
||||
sprite_object_set_selected(card_object->sprite_object, selected);
|
||||
card_object->selected = selected;
|
||||
}
|
||||
|
||||
bool card_object_is_selected(CardObject* card_object)
|
||||
{
|
||||
if (card_object == NULL)
|
||||
return false;
|
||||
return sprite_object_is_selected(card_object->sprite_object);
|
||||
return card_object->selected;
|
||||
}
|
||||
|
||||
Sprite* card_object_get_sprite(CardObject* card_object)
|
||||
|
|
|
|||
29
source/font.c
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#include "font.h"
|
||||
|
||||
#include "util.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
static const char* s_font_point_lookup[] = {
|
||||
FP0_STR,
|
||||
FP1_STR,
|
||||
FP2_STR,
|
||||
FP3_STR,
|
||||
FP4_STR,
|
||||
FP5_STR,
|
||||
FP6_STR,
|
||||
FP7_STR,
|
||||
FP8_STR,
|
||||
FP9_STR,
|
||||
};
|
||||
|
||||
const char* get_font_point_str(int val)
|
||||
{
|
||||
val = abs(val) % 10;
|
||||
return s_font_point_lookup[val];
|
||||
}
|
||||
|
||||
char digit_char_to_font_point(char digit_char)
|
||||
{
|
||||
return get_font_point_str(digit_char - '0')[0];
|
||||
}
|
||||
6426
source/game.c
|
|
@ -1,15 +1,30 @@
|
|||
#include <tonc_core.h>
|
||||
#include <tonc_tte.h>
|
||||
#include <tonc_math.h>
|
||||
|
||||
#include "util.h"
|
||||
#include "graphic_utils.h"
|
||||
|
||||
const Rect FULL_SCREENBLOCK_RECT = { 0, 0, SE_ROW_LEN - 1, SE_COL_LEN - 1};
|
||||
#include "util.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <tonc_core.h>
|
||||
#include <tonc_math.h>
|
||||
#include <tonc_tte.h>
|
||||
|
||||
const Rect FULL_SCREENBLOCK_RECT = {0, 0, SE_ROW_LEN - 1, SE_COL_LEN - 1};
|
||||
|
||||
static void clip_se_rect_to_screenblock(Rect* rect);
|
||||
static void bg_se_copy_or_move_rect_1_tile_vert(
|
||||
u16 bg_sbb,
|
||||
Rect se_rect,
|
||||
enum ScreenVertDir direction,
|
||||
bool move
|
||||
);
|
||||
static void main_bg_se_copy_or_move_rect_1_tile_vert(
|
||||
Rect se_rect,
|
||||
enum ScreenVertDir direction,
|
||||
bool move
|
||||
);
|
||||
|
||||
// Clips a rect of screenblock entries to a specified rect
|
||||
// The bounding rect is not required to be within screenblock boundaries
|
||||
static void clip_se_rect_to_bounding_rect(Rect* rect, const Rect* bounding_rect)
|
||||
static inline void clip_se_rect_to_bounding_rect(Rect* rect, const Rect* bounding_rect)
|
||||
{
|
||||
rect->right = min(rect->right, bounding_rect->right);
|
||||
rect->bottom = min(rect->bottom, bounding_rect->bottom);
|
||||
|
|
@ -24,46 +39,35 @@ static void clip_se_rect_to_screenblock(Rect* rect)
|
|||
clip_se_rect_to_bounding_rect(rect, &FULL_SCREENBLOCK_RECT);
|
||||
}
|
||||
|
||||
SE main_bg_se_get_se(BG_POINT pos)
|
||||
{
|
||||
return se_mat[MAIN_BG_SBB][pos.y][pos.x];
|
||||
}
|
||||
|
||||
// Clips a rect of screenblock entries to be within one step of
|
||||
// Clips a rect of screenblock entries to be within one step of
|
||||
// screenblock boundaries vertically depending on direction.
|
||||
static void clip_se_rect_within_step_of_full_screen_vert(Rect* se_rect, int direction)
|
||||
static inline void clip_se_rect_within_step_of_full_screen_vert(
|
||||
Rect* se_rect,
|
||||
enum ScreenVertDir direction
|
||||
)
|
||||
{
|
||||
Rect bounding_rect = FULL_SCREENBLOCK_RECT;
|
||||
if (direction == SE_UP)
|
||||
if (direction == SCREEN_UP)
|
||||
{
|
||||
bounding_rect.top += 1;
|
||||
}
|
||||
else if (direction == SE_DOWN)
|
||||
else if (direction == SCREEN_DOWN)
|
||||
{
|
||||
bounding_rect.bottom -= 1;
|
||||
}
|
||||
|
||||
|
||||
clip_se_rect_to_bounding_rect(se_rect, &bounding_rect);
|
||||
}
|
||||
|
||||
void main_bg_se_clear_rect(Rect se_rect)
|
||||
{
|
||||
if (se_rect.left > se_rect.right)
|
||||
return;
|
||||
// Clip to avoid screenblock overflow
|
||||
clip_se_rect_to_screenblock(&se_rect);
|
||||
|
||||
for (int y = se_rect.top; y < se_rect.bottom; y++)
|
||||
{
|
||||
memset16(&(se_mat[MAIN_BG_SBB][y][se_rect.left]), 0x0000, rect_width(&se_rect));
|
||||
}
|
||||
}
|
||||
|
||||
// Internal static function to merge implementation of move/copy functions.
|
||||
static void bg_se_copy_or_move_rect_1_tile_vert(u16 bg_sbb, Rect se_rect, int direction, bool move)
|
||||
static void bg_se_copy_or_move_rect_1_tile_vert(
|
||||
u16 bg_sbb,
|
||||
Rect se_rect,
|
||||
enum ScreenVertDir direction,
|
||||
bool move
|
||||
)
|
||||
{
|
||||
if (se_rect.left > se_rect.right
|
||||
|| (direction != SE_UP && direction != SE_DOWN))
|
||||
if (se_rect.left > se_rect.right || (direction != SCREEN_UP && direction != SCREEN_DOWN))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -71,14 +75,16 @@ static void bg_se_copy_or_move_rect_1_tile_vert(u16 bg_sbb, Rect se_rect, int di
|
|||
// Clip to avoid read/write overflow of the screenblock
|
||||
clip_se_rect_within_step_of_full_screen_vert(&se_rect, direction);
|
||||
|
||||
int start = (direction == SE_UP) ? se_rect.top : se_rect.bottom;
|
||||
int end = (direction == SE_UP) ? se_rect.bottom : se_rect.top;
|
||||
int start = (direction == SCREEN_UP) ? se_rect.top : se_rect.bottom;
|
||||
int end = (direction == SCREEN_UP) ? se_rect.bottom : se_rect.top;
|
||||
|
||||
for (int y = start; y != end - direction; y -= direction)
|
||||
{
|
||||
memcpy16(&(se_mat[bg_sbb][y + direction][se_rect.left]),
|
||||
&se_mat[bg_sbb][y][se_rect.left],
|
||||
rect_width(&se_rect));
|
||||
memcpy16(
|
||||
&(se_mat[bg_sbb][y + direction][se_rect.left]),
|
||||
&se_mat[bg_sbb][y][se_rect.left],
|
||||
rect_width(&se_rect)
|
||||
);
|
||||
}
|
||||
|
||||
if (move)
|
||||
|
|
@ -87,32 +93,36 @@ static void bg_se_copy_or_move_rect_1_tile_vert(u16 bg_sbb, Rect se_rect, int di
|
|||
}
|
||||
}
|
||||
|
||||
static void main_bg_se_copy_or_move_rect_1_tile_vert(Rect se_rect, int direction, bool move)
|
||||
static void main_bg_se_copy_or_move_rect_1_tile_vert(
|
||||
Rect se_rect,
|
||||
enum ScreenVertDir direction,
|
||||
bool move
|
||||
)
|
||||
{
|
||||
bg_se_copy_or_move_rect_1_tile_vert(MAIN_BG_SBB, se_rect, direction, move);
|
||||
bg_se_copy_or_move_rect_1_tile_vert(MAIN_BG_SBB, se_rect, direction, move);
|
||||
}
|
||||
|
||||
void bg_se_copy_rect_1_tile_vert(u16 bg_sbb, Rect se_rect, int direction)
|
||||
void bg_se_copy_rect_1_tile_vert(u16 bg_sbb, Rect se_rect, enum ScreenVertDir direction)
|
||||
{
|
||||
bg_se_copy_or_move_rect_1_tile_vert(MAIN_BG_SBB, se_rect, direction, false);
|
||||
}
|
||||
|
||||
void bg_se_move_rect_1_tile_vert(u16 bg_sbb, Rect se_rect, int direction)
|
||||
void bg_se_move_rect_1_tile_vert(u16 bg_sbb, Rect se_rect, enum ScreenVertDir direction)
|
||||
{
|
||||
bg_se_copy_or_move_rect_1_tile_vert(MAIN_BG_SBB, se_rect, direction, true);
|
||||
}
|
||||
|
||||
void main_bg_se_copy_rect_1_tile_vert(Rect se_rect, int direction)
|
||||
void main_bg_se_copy_rect_1_tile_vert(Rect se_rect, enum ScreenVertDir direction)
|
||||
{
|
||||
main_bg_se_copy_or_move_rect_1_tile_vert(se_rect, direction, false);
|
||||
}
|
||||
|
||||
void main_bg_se_move_rect_1_tile_vert(Rect se_rect, int direction)
|
||||
void main_bg_se_move_rect_1_tile_vert(Rect se_rect, enum ScreenVertDir direction)
|
||||
{
|
||||
main_bg_se_copy_or_move_rect_1_tile_vert(se_rect, direction, true);
|
||||
}
|
||||
|
||||
void main_bg_se_copy_rect(Rect se_rect, BG_POINT pos)
|
||||
void main_bg_se_copy_rect(Rect se_rect, BG_POINT dest_pos)
|
||||
{
|
||||
if (se_rect.left > se_rect.right || se_rect.top > se_rect.bottom)
|
||||
return;
|
||||
|
|
@ -127,22 +137,18 @@ void main_bg_se_copy_rect(Rect se_rect, BG_POINT pos)
|
|||
// Copy the rect to the tile map
|
||||
for (int sy = 0; sy < height; sy++)
|
||||
{
|
||||
memcpy16(&tile_map[sy][0],
|
||||
&se_mat[MAIN_BG_SBB][se_rect.top + sy][se_rect.left],
|
||||
width);
|
||||
memcpy16(&tile_map[sy][0], &se_mat[MAIN_BG_SBB][se_rect.top + sy][se_rect.left], width);
|
||||
}
|
||||
|
||||
|
||||
// TODO: Avoid overflow
|
||||
// Copy the tilemap to the new rect position
|
||||
for (int sy = 0; sy < height; sy++)
|
||||
{
|
||||
memcpy16(&se_mat[MAIN_BG_SBB][pos.y + sy][pos.x],
|
||||
&tile_map[sy][0],
|
||||
width);
|
||||
memcpy16(&se_mat[MAIN_BG_SBB][dest_pos.y + sy][dest_pos.x], &tile_map[sy][0], width);
|
||||
}
|
||||
}
|
||||
|
||||
void main_bg_se_fill_rect_with_se(SE se, Rect se_rect)
|
||||
static inline void main_bg_se_fill_rect_with_se(SE se, Rect se_rect)
|
||||
{
|
||||
if (se_rect.left > se_rect.right || se_rect.top > se_rect.bottom)
|
||||
return;
|
||||
|
|
@ -160,7 +166,12 @@ void main_bg_se_fill_rect_with_se(SE se, Rect se_rect)
|
|||
}
|
||||
|
||||
// Helper: Copy the corners of a 3x3 tile block
|
||||
static void main_bg_se_expand_3x3_copy_corners(const Rect* se_dest_rect, const BG_POINT* src_top_left_pnt, int dest_rect_width, int dest_rect_height)
|
||||
static inline void main_bg_se_expand_3x3_copy_corners(
|
||||
const Rect* se_dest_rect,
|
||||
const BG_POINT* src_top_left_pnt,
|
||||
int dest_rect_width,
|
||||
int dest_rect_height
|
||||
)
|
||||
{
|
||||
SE top_left_se = se_mat[MAIN_BG_SBB][src_top_left_pnt->y][src_top_left_pnt->x];
|
||||
se_mat[MAIN_BG_SBB][se_dest_rect->top][se_dest_rect->left] = top_left_se;
|
||||
|
|
@ -169,33 +180,54 @@ static void main_bg_se_expand_3x3_copy_corners(const Rect* se_dest_rect, const B
|
|||
se_mat[MAIN_BG_SBB][se_dest_rect->top][se_dest_rect->left + dest_rect_width - 1] = top_right_se;
|
||||
|
||||
SE bottom_left_se = se_mat[MAIN_BG_SBB][src_top_left_pnt->y + 2][src_top_left_pnt->x];
|
||||
se_mat[MAIN_BG_SBB][se_dest_rect->top + dest_rect_height - 1][se_dest_rect->left] = bottom_left_se;
|
||||
se_mat[MAIN_BG_SBB][se_dest_rect->top + dest_rect_height - 1][se_dest_rect->left] =
|
||||
bottom_left_se;
|
||||
|
||||
SE bottom_right_se = se_mat[MAIN_BG_SBB][src_top_left_pnt->y + 2][src_top_left_pnt->x + 2];
|
||||
se_mat[MAIN_BG_SBB][se_dest_rect->top + dest_rect_height - 1][se_dest_rect->left + dest_rect_width - 1] = bottom_right_se;
|
||||
se_mat[MAIN_BG_SBB][se_dest_rect->top + dest_rect_height - 1]
|
||||
[se_dest_rect->left + dest_rect_width - 1] = bottom_right_se;
|
||||
}
|
||||
|
||||
// Helper: Copy the top and bottom sides of a 3x3 tile block
|
||||
static void main_bg_se_expand_3x3_copy_top_bottom(const Rect* se_dest_rect, const BG_POINT* src_top_left_pnt, int dest_rect_width)
|
||||
static inline void main_bg_se_expand_3x3_copy_top_bottom(
|
||||
const Rect* se_dest_rect,
|
||||
const BG_POINT* src_top_left_pnt,
|
||||
int dest_rect_width
|
||||
)
|
||||
{
|
||||
if (dest_rect_width > 2)
|
||||
{
|
||||
SE top_middle_se = se_mat[MAIN_BG_SBB][src_top_left_pnt->y][src_top_left_pnt->x + 1];
|
||||
SE bottom_middle_se = se_mat[MAIN_BG_SBB][src_top_left_pnt->y + 2][src_top_left_pnt->x + 1];
|
||||
memset16(&se_mat[MAIN_BG_SBB][se_dest_rect->top][se_dest_rect->left + 1], top_middle_se, dest_rect_width - 2);
|
||||
memset16(&se_mat[MAIN_BG_SBB][se_dest_rect->bottom][se_dest_rect->left + 1], bottom_middle_se, dest_rect_width - 2);
|
||||
memset16(
|
||||
&se_mat[MAIN_BG_SBB][se_dest_rect->top][se_dest_rect->left + 1],
|
||||
top_middle_se,
|
||||
dest_rect_width - 2
|
||||
);
|
||||
memset16(
|
||||
&se_mat[MAIN_BG_SBB][se_dest_rect->bottom][se_dest_rect->left + 1],
|
||||
bottom_middle_se,
|
||||
dest_rect_width - 2
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper: Copy the left and right sides of a 3x3 tile block
|
||||
static void main_bg_se_expand_3x3_copy_left_right(const Rect* se_dest_rect, const BG_POINT* src_top_left_pnt, int dest_rect_width, int dest_rect_height)
|
||||
static inline void main_bg_se_3w_copy_expand_left_right_sides(
|
||||
const Rect* se_dest_rect,
|
||||
const BG_POINT* src_left_pnt
|
||||
)
|
||||
{
|
||||
SE middle_left_se = se_mat[MAIN_BG_SBB][src_top_left_pnt->y + 1][src_top_left_pnt->x];
|
||||
SE middle_right_se = se_mat[MAIN_BG_SBB][src_top_left_pnt->y + 1][src_top_left_pnt->x + 2];
|
||||
for (int y = 1; y < dest_rect_height - 1; y++)
|
||||
SE middle_left_se = se_mat[MAIN_BG_SBB][src_left_pnt->y][src_left_pnt->x];
|
||||
// Assuming width 3 so the right side is + 2
|
||||
SE middle_right_se = se_mat[MAIN_BG_SBB][src_left_pnt->y][src_left_pnt->x + 2];
|
||||
int dest_rect_width = rect_width(se_dest_rect);
|
||||
int dest_rect_height = rect_height(se_dest_rect);
|
||||
for (int y = 0; y < dest_rect_height; y++)
|
||||
{
|
||||
se_mat[MAIN_BG_SBB][se_dest_rect->top + y][se_dest_rect->left] = middle_left_se;
|
||||
se_mat[MAIN_BG_SBB][se_dest_rect->top + y][se_dest_rect->left + dest_rect_width - 1] = middle_right_se;
|
||||
se_mat[MAIN_BG_SBB][se_dest_rect->top + y][se_dest_rect->left + dest_rect_width - 1] =
|
||||
middle_right_se;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -213,19 +245,60 @@ void main_bg_se_copy_expand_3x3_rect(Rect se_dest_rect, BG_POINT src_top_left_pn
|
|||
}
|
||||
|
||||
// Copy the corners
|
||||
main_bg_se_expand_3x3_copy_corners(&se_dest_rect, &src_top_left_pnt, dest_rect_width, dest_rect_height);
|
||||
main_bg_se_expand_3x3_copy_corners(
|
||||
&se_dest_rect,
|
||||
&src_top_left_pnt,
|
||||
dest_rect_width,
|
||||
dest_rect_height
|
||||
);
|
||||
|
||||
// Copy top and bottom sides
|
||||
main_bg_se_expand_3x3_copy_top_bottom(&se_dest_rect, &src_top_left_pnt, dest_rect_width);
|
||||
|
||||
BG_POINT src_middle_left_pnt = {src_top_left_pnt.x, src_top_left_pnt.y + 1};
|
||||
|
||||
// Avoid the corners when copying the sides
|
||||
Rect dest_sides_rect = se_dest_rect;
|
||||
dest_sides_rect.top += 1;
|
||||
dest_sides_rect.bottom -= 1;
|
||||
|
||||
// Copy left and right sides
|
||||
main_bg_se_expand_3x3_copy_left_right(&se_dest_rect, &src_top_left_pnt, dest_rect_width, dest_rect_height);
|
||||
main_bg_se_3w_copy_expand_left_right_sides(&dest_sides_rect, &src_middle_left_pnt);
|
||||
|
||||
// Fill the center if needed
|
||||
if (dest_rect_width > 2 && dest_rect_height > 2)
|
||||
{
|
||||
SE middle_fill_se = se_mat[MAIN_BG_SBB][src_top_left_pnt.y + 1][src_top_left_pnt.x + 1];
|
||||
Rect dest_inner_fill_rect = {se_dest_rect.left + 1, se_dest_rect.top + 1, se_dest_rect.right - 1, se_dest_rect.bottom - 1};
|
||||
Rect dest_inner_fill_rect = {
|
||||
se_dest_rect.left + 1,
|
||||
se_dest_rect.top + 1,
|
||||
se_dest_rect.right - 1,
|
||||
se_dest_rect.bottom - 1
|
||||
};
|
||||
main_bg_se_fill_rect_with_se(middle_fill_se, dest_inner_fill_rect);
|
||||
}
|
||||
}
|
||||
|
||||
void main_bg_se_copy_expand_3w_row(Rect se_dest_rect, BG_POINT src_row_left_pnt)
|
||||
{
|
||||
clip_se_rect_to_screenblock(&se_dest_rect);
|
||||
int dest_rect_width = rect_width(&se_dest_rect);
|
||||
if (dest_rect_width < 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Copy left and right sides
|
||||
main_bg_se_3w_copy_expand_left_right_sides(&se_dest_rect, &src_row_left_pnt);
|
||||
|
||||
if (dest_rect_width > 2)
|
||||
{
|
||||
SE middle_fill_se = se_mat[MAIN_BG_SBB][src_row_left_pnt.y][src_row_left_pnt.x + 1];
|
||||
Rect dest_inner_fill_rect = se_dest_rect;
|
||||
|
||||
// Avoid copying the sides when filling the rect.
|
||||
dest_inner_fill_rect.left += 1;
|
||||
dest_inner_fill_rect.right -= 1;
|
||||
main_bg_se_fill_rect_with_se(middle_fill_se, dest_inner_fill_rect);
|
||||
}
|
||||
}
|
||||
|
|
@ -235,22 +308,45 @@ void tte_erase_rect_wrapper(Rect rect)
|
|||
tte_erase_rect(rect.left, rect.top, rect.right, rect.bottom);
|
||||
}
|
||||
|
||||
void update_text_rect_to_right_align_num(Rect* rect, int num, int overflow_direction)
|
||||
void update_text_rect_to_right_align_str(
|
||||
Rect* rect,
|
||||
const char* str,
|
||||
enum OverflowDir overflow_direction
|
||||
)
|
||||
{
|
||||
int num_digits = get_digits(num);
|
||||
// TODO: Allow passing string length to avoid calling strlen()?
|
||||
int str_len = strlen(str);
|
||||
if (overflow_direction == OVERFLOW_LEFT)
|
||||
{
|
||||
rect->left = max(0, rect->right - num_digits * TILE_SIZE);
|
||||
rect->left = max(0, rect->right - str_len * TTE_CHAR_SIZE);
|
||||
}
|
||||
else if (overflow_direction == OVERFLOW_RIGHT)
|
||||
{
|
||||
int num_fitting_digits = rect_width(rect) / TILE_SIZE;
|
||||
if (num_digits < num_fitting_digits)
|
||||
rect->left += (num_fitting_digits - num_digits) * TILE_SIZE;
|
||||
//else nothing is to be updated, entire rect is filled and may overflow
|
||||
int num_fitting_chars = rect_width(rect) / TTE_CHAR_SIZE;
|
||||
if (str_len < num_fitting_chars)
|
||||
rect->left += (num_fitting_chars - str_len) * TTE_CHAR_SIZE;
|
||||
// else nothing is to be updated, entire rect is filled and may overflow
|
||||
}
|
||||
}
|
||||
|
||||
void update_text_rect_to_center_str(Rect* rect, const char* str, enum ScreenHorzDir bias_direction)
|
||||
{
|
||||
if (rect == NULL || str == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int text_width_chars = strlen(str);
|
||||
int rect_width_chars = rect_width(rect) / TTE_CHAR_SIZE;
|
||||
|
||||
bool bias_right = (bias_direction == SCREEN_RIGHT);
|
||||
|
||||
/* Adding bias_right makes sure that we round up when biased right
|
||||
* but round down when biased left.
|
||||
*/
|
||||
rect->left += max(0, (rect_width_chars - text_width_chars + bias_right) / 2) * TTE_CHAR_SIZE;
|
||||
}
|
||||
|
||||
void memcpy16_tile8_with_palette_offset(u16* dst, const u16* src, uint hwcount, u8 palette_offset)
|
||||
{
|
||||
const u16 offset = (((palette_offset) << 8) | (palette_offset));
|
||||
|
|
@ -263,7 +359,8 @@ void memcpy16_tile8_with_palette_offset(u16* dst, const u16* src, uint hwcount,
|
|||
|
||||
void memcpy32_tile8_with_palette_offset(u32* dst, const u32* src, uint wcount, u8 palette_offset)
|
||||
{
|
||||
const u32 offset = (palette_offset << 24) | (palette_offset << 16) | (palette_offset << 8) | palette_offset;
|
||||
const u32 offset =
|
||||
(palette_offset << 24) | (palette_offset << 16) | (palette_offset << 8) | palette_offset;
|
||||
for (int i = 0; i < wcount; i++)
|
||||
{
|
||||
// Copying u8 data 4 times across u32 data
|
||||
|
|
@ -299,4 +396,17 @@ void toggle_windows(bool win0, bool win1)
|
|||
{
|
||||
REG_BLDCNT = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void main_bg_se_clear_rect(Rect se_rect)
|
||||
{
|
||||
if (se_rect.left > se_rect.right)
|
||||
return;
|
||||
// Clip to avoid screenblock overflow
|
||||
clip_se_rect_to_screenblock(&se_rect);
|
||||
|
||||
for (int y = se_rect.top; y < se_rect.bottom; y++)
|
||||
{
|
||||
memset16(&(se_mat[MAIN_BG_SBB][y][se_rect.left]), 0x0000, rect_width(&se_rect));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,41 +1,68 @@
|
|||
#include "hand_analysis.h"
|
||||
|
||||
#include "card.h"
|
||||
#include "game.h"
|
||||
|
||||
static void get_distribution(CardObject **cards, int top, u8 *ranks_out, u8 *suits_out) {
|
||||
for (int i = 0; i < NUM_RANKS; i++) ranks_out[i] = 0;
|
||||
for (int i = 0; i < NUM_SUITS; i++) suits_out[i] = 0;
|
||||
void get_hand_distribution(u8 ranks_out[NUM_RANKS], u8 suits_out[NUM_SUITS])
|
||||
{
|
||||
for (int i = 0; i < NUM_RANKS; i++)
|
||||
ranks_out[i] = 0;
|
||||
for (int i = 0; i < NUM_SUITS; i++)
|
||||
suits_out[i] = 0;
|
||||
|
||||
for (int i = 0; i <= top; i++) {
|
||||
if (cards[i] && card_object_is_selected(cards[i])) {
|
||||
CardObject** cards = get_hand_array();
|
||||
int top = get_hand_top();
|
||||
for (int i = 0; i <= top; i++)
|
||||
{
|
||||
if (cards[i] && card_object_is_selected(cards[i]))
|
||||
{
|
||||
ranks_out[cards[i]->card->rank]++;
|
||||
suits_out[cards[i]->card->suit]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void get_hand_distribution(u8 *ranks_out, u8 *suits_out) {
|
||||
get_distribution(get_hand_array(), get_hand_top(), ranks_out, suits_out);
|
||||
}
|
||||
void get_played_distribution(u8 ranks_out[NUM_RANKS], u8 suits_out[NUM_SUITS])
|
||||
{
|
||||
for (int i = 0; i < NUM_RANKS; i++)
|
||||
ranks_out[i] = 0;
|
||||
for (int i = 0; i < NUM_SUITS; i++)
|
||||
suits_out[i] = 0;
|
||||
|
||||
void get_played_distribution(u8 *ranks_out, u8 *suits_out) {
|
||||
get_distribution(get_played_array(), get_played_top(), ranks_out, suits_out);
|
||||
CardObject** played = get_played_array();
|
||||
int top = get_played_top();
|
||||
for (int i = 0; i <= top; i++)
|
||||
{
|
||||
/* The difference from get_hand_distribution() (not checking if card is selected)
|
||||
* is in line Balatro behavior,
|
||||
* see https://github.com/GBALATRO/balatro-gba/issues/341#issuecomment-3691363488
|
||||
*/
|
||||
if (!played[i])
|
||||
continue;
|
||||
ranks_out[played[i]->card->rank]++;
|
||||
suits_out[played[i]->card->suit]++;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the highest N of a kind. So a full-house would return 3.
|
||||
u8 hand_contains_n_of_a_kind(u8 *ranks) {
|
||||
u8 hand_contains_n_of_a_kind(u8* ranks)
|
||||
{
|
||||
u8 highest_n = 0;
|
||||
for (int i = 0; i < NUM_RANKS; i++) {
|
||||
for (int i = 0; i < NUM_RANKS; i++)
|
||||
{
|
||||
if (ranks[i] > highest_n)
|
||||
highest_n = ranks[i];
|
||||
}
|
||||
return highest_n;
|
||||
}
|
||||
|
||||
bool hand_contains_two_pair(u8 *ranks) {
|
||||
bool hand_contains_two_pair(u8* ranks)
|
||||
{
|
||||
bool contains_other_pair = false;
|
||||
for (int i = 0; i < NUM_RANKS; i++) {
|
||||
if (ranks[i] >= 2) {
|
||||
for (int i = 0; i < NUM_RANKS; i++)
|
||||
{
|
||||
if (ranks[i] >= 2)
|
||||
{
|
||||
if (contains_other_pair)
|
||||
return true;
|
||||
contains_other_pair = true;
|
||||
|
|
@ -44,14 +71,18 @@ bool hand_contains_two_pair(u8 *ranks) {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool hand_contains_full_house(u8* ranks) {
|
||||
bool hand_contains_full_house(u8* ranks)
|
||||
{
|
||||
int count_three = 0;
|
||||
int count_pair = 0;
|
||||
for (int i = 0; i < NUM_RANKS; i++) {
|
||||
if (ranks[i] >= 3) {
|
||||
for (int i = 0; i < NUM_RANKS; i++)
|
||||
{
|
||||
if (ranks[i] >= 3)
|
||||
{
|
||||
count_three++;
|
||||
}
|
||||
else if (ranks[i] >= 2) {
|
||||
else if (ranks[i] >= 2)
|
||||
{
|
||||
count_pair++;
|
||||
}
|
||||
}
|
||||
|
|
@ -63,26 +94,363 @@ bool hand_contains_full_house(u8* ranks) {
|
|||
return (count_three >= 2 || (count_three && count_pair));
|
||||
}
|
||||
|
||||
bool hand_contains_straight(u8 *ranks) {
|
||||
for (int i = 0; i < NUM_RANKS - 4; i++)
|
||||
// This is mostly from Google Gemini
|
||||
bool hand_contains_straight(u8* ranks)
|
||||
{
|
||||
if (!is_shortcut_joker_active())
|
||||
{
|
||||
if (ranks[i] && ranks[i + 1] && ranks[i + 2] && ranks[i + 3] && ranks[i + 4])
|
||||
return true;
|
||||
int straight_size = get_straight_and_flush_size();
|
||||
// This is the regular case of detecting straights
|
||||
int run = 0;
|
||||
for (int i = 0; i < NUM_RANKS; ++i)
|
||||
{
|
||||
if (ranks[i])
|
||||
{
|
||||
if (++run >= straight_size)
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
run = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for ace low straight
|
||||
if (straight_size >= 2 && ranks[ACE])
|
||||
{
|
||||
// With A as low, the highest rank you can use is FIVE.
|
||||
// -1 for inclusive integer distance and another -1 for the Ace e.g. need=5 -> need 2..5
|
||||
int last_needed = TWO + (straight_size - 2);
|
||||
if (last_needed <= FIVE)
|
||||
{
|
||||
bool ok = true;
|
||||
for (int r = TWO; r <= last_needed; ++r)
|
||||
{
|
||||
if (!ranks[r])
|
||||
{
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ok)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Shortcut Joker is active, we have to detect straights where any card may "skip" 1 rank
|
||||
// We do this with a dynamic programming algorithm that calculates
|
||||
// the longest possible straight that can end on each rank
|
||||
// and stopping when we find one that is {straight-size} cards long
|
||||
u8 longest_short_cut_at[NUM_RANKS] = {0};
|
||||
|
||||
// A low ace can start a sequence. 'ace_low_len' is 1 if an ace is present,
|
||||
// acting as a potential predecessor for TWO and THREE.
|
||||
int ace_low_len = ranks[ACE] ? 1 : 0;
|
||||
|
||||
// Iterate through all ranks from TWO up to ACE.
|
||||
for (int i = 0; i < NUM_RANKS; i++)
|
||||
{
|
||||
// No cards in this rank, no straight can end here, continue
|
||||
if (ranks[i] == 0)
|
||||
{
|
||||
longest_short_cut_at[i] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
int prev_len1 = 0;
|
||||
int prev_len2 = 0;
|
||||
|
||||
// This logic handles the special connections for ace-low straights.
|
||||
if (i == TWO)
|
||||
{
|
||||
// A TWO can be preceded by a low ACE (no skip).
|
||||
prev_len1 = ace_low_len;
|
||||
}
|
||||
else if (i == THREE)
|
||||
{
|
||||
// A THREE can be preceded by a TWO (no skip) or a low ACE (skip).
|
||||
prev_len1 = longest_short_cut_at[TWO];
|
||||
prev_len2 = ace_low_len;
|
||||
}
|
||||
else if (i == ACE)
|
||||
{
|
||||
// An ACE (as the highest card) can be preceded by a KING or a QUEEN.
|
||||
prev_len1 = longest_short_cut_at[KING];
|
||||
prev_len2 = longest_short_cut_at[QUEEN];
|
||||
}
|
||||
else // For all other cards (FOUR through KING).
|
||||
{
|
||||
// A card can be preceded by the rank directly below or two ranks below.
|
||||
prev_len1 = longest_short_cut_at[i - 1];
|
||||
prev_len2 = longest_short_cut_at[i - 2];
|
||||
}
|
||||
|
||||
// The length of the straight ending at rank 'i' is 1 (for the card itself)
|
||||
// plus the length of the longest valid preceding straight.
|
||||
longest_short_cut_at[i] = 1 + max(prev_len1, prev_len2);
|
||||
|
||||
// If we've formed a sequence of {straight-size} or more cards, we have a straight.
|
||||
if (longest_short_cut_at[i] >= get_straight_and_flush_size())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check for ace low straight
|
||||
if (ranks[ACE] && ranks[TWO] && ranks[THREE] && ranks[FOUR] && ranks[FIVE])
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool hand_contains_flush(u8 *suits) {
|
||||
bool hand_contains_flush(u8* suits)
|
||||
{
|
||||
for (int i = 0; i < NUM_SUITS; i++)
|
||||
{
|
||||
if (suits[i] >= MAX_SELECTION_SIZE) // this allows MAX_SELECTION_SIZE - 1 for four fingers joker
|
||||
if (suits[i] >= get_straight_and_flush_size())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Returns the number of cards in the best flush found
|
||||
// or 0 if no flush of min_len is found, and marks them in out_selection.
|
||||
/**
|
||||
* Finds the largest flush (set of cards with the same suit) in the given array of played cards.
|
||||
* Marks the cards belonging to the best flush in the out_selection array.
|
||||
*
|
||||
* @param played Array of pointers to CardObject representing played cards.
|
||||
* @param top Index of the top of the played stack.
|
||||
* @param min_len Minimum number of cards required for a flush.
|
||||
* @param out_selection Output array of bools; set to true for cards in the best flush, false
|
||||
* otherwise.
|
||||
* @return The number of cards in the best flush found, or 0 if no flush meets min_len.
|
||||
*/
|
||||
int find_flush_in_played_cards(CardObject** played, int top, int min_len, bool* out_selection)
|
||||
{
|
||||
if (top < 0)
|
||||
return 0;
|
||||
for (int i = 0; i <= top; i++)
|
||||
out_selection[i] = false;
|
||||
|
||||
int suit_counts[NUM_SUITS] = {0};
|
||||
for (int i = 0; i <= top; i++)
|
||||
{
|
||||
if (played[i] && played[i]->card)
|
||||
{
|
||||
suit_counts[played[i]->card->suit]++;
|
||||
}
|
||||
}
|
||||
|
||||
int best_suit = -1;
|
||||
int best_count = 0;
|
||||
for (int i = 0; i < NUM_SUITS; i++)
|
||||
{
|
||||
if (suit_counts[i] > best_count)
|
||||
{
|
||||
best_count = suit_counts[i];
|
||||
best_suit = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (best_count >= min_len)
|
||||
{
|
||||
for (int i = 0; i <= top; i++)
|
||||
{
|
||||
if (played[i] && played[i]->card && played[i]->card->suit == best_suit)
|
||||
{
|
||||
out_selection[i] = true;
|
||||
}
|
||||
}
|
||||
return best_count;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Returns the number of cards in the best straight or 0 if no straight of min_len is found, marks
|
||||
// as true them in out_selection[]. This is mostly from Google Gemini
|
||||
int find_straight_in_played_cards(
|
||||
CardObject** played,
|
||||
int top,
|
||||
bool shortcut_active,
|
||||
int min_len,
|
||||
bool* out_selection
|
||||
)
|
||||
{
|
||||
if (top < 0)
|
||||
return 0;
|
||||
for (int i = 0; i <= top; i++)
|
||||
out_selection[i] = false;
|
||||
|
||||
// --- Setup for Backtracking DP ---
|
||||
u8 longest_straight_at[NUM_RANKS] = {0};
|
||||
int parent[NUM_RANKS];
|
||||
for (int i = 0; i < NUM_RANKS; i++)
|
||||
parent[i] = -1;
|
||||
|
||||
u8 ranks[NUM_RANKS] = {0};
|
||||
for (int i = 0; i <= top; i++)
|
||||
{
|
||||
if (played[i] && played[i]->card)
|
||||
{
|
||||
ranks[played[i]->card->rank]++;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Run DP to find longest straight ---
|
||||
// This is nearly identical to hand_contains_straight() logic
|
||||
// TODO: Consolidate functions to avoid code duplication?
|
||||
// Might cost performance because this does a little more
|
||||
int ace_low_len = ranks[ACE] ? 1 : 0;
|
||||
for (int i = 0; i < NUM_RANKS; i++)
|
||||
{
|
||||
if (ranks[i] > 0)
|
||||
{
|
||||
int prev1 = 0, prev2 = 0;
|
||||
int parent1 = -1, parent2 = -1;
|
||||
|
||||
if (shortcut_active)
|
||||
{
|
||||
if (i == TWO)
|
||||
{
|
||||
prev1 = ace_low_len;
|
||||
parent1 = ACE;
|
||||
}
|
||||
else if (i == THREE)
|
||||
{
|
||||
prev1 = longest_straight_at[TWO];
|
||||
parent1 = TWO;
|
||||
prev2 = ace_low_len;
|
||||
parent2 = ACE;
|
||||
}
|
||||
else if (i == ACE)
|
||||
{
|
||||
prev1 = longest_straight_at[KING];
|
||||
parent1 = KING;
|
||||
prev2 = longest_straight_at[QUEEN];
|
||||
parent2 = QUEEN;
|
||||
}
|
||||
else
|
||||
{
|
||||
prev1 = longest_straight_at[i - 1];
|
||||
parent1 = i - 1;
|
||||
if (i > 1)
|
||||
{
|
||||
prev2 = longest_straight_at[i - 2];
|
||||
parent2 = i - 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (i == TWO)
|
||||
{
|
||||
prev1 = ace_low_len;
|
||||
parent1 = ACE;
|
||||
}
|
||||
else if (i == ACE)
|
||||
{
|
||||
prev1 = longest_straight_at[KING];
|
||||
parent1 = KING;
|
||||
}
|
||||
else
|
||||
{
|
||||
prev1 = longest_straight_at[i - 1];
|
||||
parent1 = i - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Parallels longest_short_cut_at[i] = 1 + max(prev_len1, prev_len2);
|
||||
// in hand_contains_straight()
|
||||
if (prev1 >= prev2)
|
||||
{
|
||||
longest_straight_at[i] = 1 + prev1;
|
||||
parent[i] = parent1;
|
||||
}
|
||||
else
|
||||
{
|
||||
longest_straight_at[i] = 1 + prev2;
|
||||
parent[i] = parent2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Find best straight and backtrack ---
|
||||
int best_len = 0;
|
||||
int end_rank = -1;
|
||||
for (int i = 0; i < NUM_RANKS; i++)
|
||||
{
|
||||
if (longest_straight_at[i] >= best_len)
|
||||
{
|
||||
best_len = longest_straight_at[i];
|
||||
end_rank = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (best_len >= min_len)
|
||||
{
|
||||
u8 needed_ranks[NUM_RANKS] = {0};
|
||||
int current_rank = end_rank;
|
||||
while (current_rank != -1 && best_len > 0)
|
||||
{
|
||||
needed_ranks[current_rank]++;
|
||||
current_rank = parent[current_rank];
|
||||
best_len--;
|
||||
}
|
||||
|
||||
for (int i = 0; i <= top; i++)
|
||||
{
|
||||
if (played[i] && played[i]->card && needed_ranks[played[i]->card->rank] > 0)
|
||||
{
|
||||
out_selection[i] = true;
|
||||
needed_ranks[played[i]->card->rank]--;
|
||||
}
|
||||
}
|
||||
|
||||
int final_card_count = 0;
|
||||
for (int i = 0; i <= top; i++)
|
||||
{
|
||||
if (out_selection[i])
|
||||
final_card_count++;
|
||||
}
|
||||
return final_card_count;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// This is used for the special case in "Four Fingers" where you can add a pair into a straight
|
||||
// (e.g. AA234 should score all 5 cards)
|
||||
void select_paired_cards_in_hand(CardObject** played, int played_top, bool* selection)
|
||||
{
|
||||
// Build a set of ranks that are already selected
|
||||
bool rank_selected[NUM_RANKS] = {0};
|
||||
bool any_selected_rank = false;
|
||||
|
||||
for (int i = 0; i <= played_top; i++)
|
||||
{
|
||||
if (selection[i] && played[i] && played[i]->card)
|
||||
{
|
||||
rank_selected[played[i]->card->rank] = true;
|
||||
any_selected_rank = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If no ranks were selected initially, nothing to do
|
||||
if (!any_selected_rank)
|
||||
return;
|
||||
|
||||
// Add any unselected card to the selection if if shares a rank with the selected ranks
|
||||
for (int i = 0; i <= played_top; i++)
|
||||
{
|
||||
if (played[i] && played[i]->card && !selection[i])
|
||||
{
|
||||
if (rank_selected[played[i]->card->rank])
|
||||
{
|
||||
selection[i] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
530
source/joker.c
|
|
@ -1,24 +1,35 @@
|
|||
#include <tonc.h>
|
||||
|
||||
#include "joker.h"
|
||||
#include "joker_gfx.h"
|
||||
#include "graphic_utils.h"
|
||||
|
||||
#include "card.h"
|
||||
#include "graphic_utils.h"
|
||||
#include "joker_gfx.h"
|
||||
#include "pool.h"
|
||||
#include "soundbank.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <maxmod.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <tonc.h>
|
||||
|
||||
#define JOKER_SCORE_TEXT_Y 48
|
||||
#define JOKER_SCORE_TEXT_Y 48
|
||||
#define HELD_CARD_SCORE_TEXT_Y 108
|
||||
#define MAX_CARD_SCORE_STR_LEN 2
|
||||
#define NUM_JOKERS_PER_SPRITESHEET 2
|
||||
|
||||
const unsigned int **joker_gfxTiles;
|
||||
const unsigned short **joker_gfxPal;
|
||||
static const unsigned int* joker_gfxTiles[] = {
|
||||
#define DEF_JOKER_GFX(idx) joker_gfx##idx##Tiles,
|
||||
#include "../include/def_joker_gfx_table.h"
|
||||
#undef DEF_JOKER_GFX
|
||||
};
|
||||
|
||||
const static u8 edition_price_lut[MAX_EDITIONS] =
|
||||
{
|
||||
static const unsigned short* joker_gfxPal[] = {
|
||||
#define DEF_JOKER_GFX(idx) joker_gfx##idx##Pal,
|
||||
#include "def_joker_gfx_table.h"
|
||||
#undef DEF_JOKER_GFX
|
||||
};
|
||||
|
||||
const static u8 edition_price_lut[MAX_EDITIONS] = {
|
||||
0, // BASE_EDITION
|
||||
2, // FOIL_EDITION
|
||||
3, // HOLO_EDITION
|
||||
|
|
@ -28,163 +39,78 @@ const static u8 edition_price_lut[MAX_EDITIONS] =
|
|||
|
||||
/* So for the card objects, I needed them to be properly sorted
|
||||
which is why they let you specify the layer index when creating a new card object.
|
||||
Since the cards would overlap a lot in your hand, If they weren't sorted properly, it would look like a mess.
|
||||
The joker objects are functionally identical to card objects, so they use the same logic.
|
||||
But I'm going to use a simpler approach for the joker objects
|
||||
since I'm lazy and sorting them wouldn't look good enough to warrant the effort.
|
||||
Since the cards would overlap a lot in your hand, If they weren't sorted properly, it would look
|
||||
like a mess. The joker objects are functionally identical to card objects, so they use the same
|
||||
logic. But I'm going to use a simpler approach for the joker objects since I'm lazy and sorting
|
||||
them wouldn't look good enough to warrant the effort.
|
||||
*/
|
||||
static bool used_layers[MAX_JOKER_OBJECTS] = {false}; // Track used layers for joker sprites
|
||||
static bool _used_layers[MAX_JOKER_OBJECTS] = {false}; // Track used layers for joker sprites
|
||||
// TODO: Refactor sorting into SpriteObject?
|
||||
|
||||
// Maps the spritesheet index to the palette bank index allocated to it.
|
||||
// Spritesheets that were not allocated are
|
||||
static int* joker_spritesheet_pb_map;
|
||||
static int joker_pb_num_sprite_users[JOKER_LAST_PB - JOKER_BASE_PB + 1] = { 0 };
|
||||
static int _joker_spritesheet_pb_map[(MAX_DEFINABLE_JOKERS + 1) / NUM_JOKERS_PER_SPRITESHEET];
|
||||
static int _joker_pb_num_sprite_users[JOKER_LAST_PB - JOKER_BASE_PB + 1] = {0};
|
||||
|
||||
static int get_num_spritesheets()
|
||||
{
|
||||
return (get_joker_registry_size() + NUM_JOKERS_PER_SPRITESHEET - 1) / NUM_JOKERS_PER_SPRITESHEET;
|
||||
}
|
||||
|
||||
static int joker_get_spritesheet_idx(u8 joker_id)
|
||||
|
||||
{
|
||||
return joker_id / NUM_JOKERS_PER_SPRITESHEET;
|
||||
}
|
||||
|
||||
// TODO: This should be generalized so any sprite can have dynamic swapping
|
||||
static void joker_pb_add_sprite_user(int pb)
|
||||
{
|
||||
joker_pb_num_sprite_users[pb - JOKER_BASE_PB]++;
|
||||
}
|
||||
|
||||
static void joker_pb_remove_sprite_user(int pb)
|
||||
{
|
||||
int num_sprite_users = joker_pb_num_sprite_users[pb - JOKER_BASE_PB];
|
||||
joker_pb_num_sprite_users[pb - JOKER_BASE_PB] = max(0, num_sprite_users - 1);
|
||||
}
|
||||
|
||||
static int joker_pb_get_num_sprite_users(int joker_pb)
|
||||
{
|
||||
return joker_pb_num_sprite_users[joker_pb - JOKER_BASE_PB];
|
||||
}
|
||||
|
||||
static int get_unused_joker_pb()
|
||||
{
|
||||
for (int i = 0; i < NUM_ELEM_IN_ARR(joker_pb_num_sprite_users); i++)
|
||||
{
|
||||
if (joker_pb_num_sprite_users[i] == 0)
|
||||
{
|
||||
return (i + JOKER_BASE_PB);
|
||||
}
|
||||
}
|
||||
|
||||
return UNDEFINED;
|
||||
}
|
||||
|
||||
static int allocate_pb_if_needed(u8 joker_id)
|
||||
{
|
||||
int joker_spritesheet_idx = joker_get_spritesheet_idx(joker_id);
|
||||
int joker_pb = joker_spritesheet_pb_map[joker_spritesheet_idx];
|
||||
if (joker_pb != UNDEFINED)
|
||||
{
|
||||
// Already allocated
|
||||
return joker_pb;
|
||||
}
|
||||
|
||||
// Allocate a new palette
|
||||
joker_pb = get_unused_joker_pb();
|
||||
|
||||
if (joker_pb == UNDEFINED)
|
||||
{
|
||||
// Ran out of palettes, default to base and pray
|
||||
joker_pb = JOKER_BASE_PB;
|
||||
}
|
||||
else
|
||||
{
|
||||
joker_spritesheet_pb_map[joker_spritesheet_idx] = joker_pb;
|
||||
memcpy16(&pal_obj_mem[PAL_ROW_LEN * joker_pb], joker_gfxPal[joker_spritesheet_idx], NUM_ELEM_IN_ARR(joker_gfx0Pal));
|
||||
}
|
||||
|
||||
return joker_pb;
|
||||
}
|
||||
static int s_get_num_spritesheets(void);
|
||||
static int s_joker_get_spritesheet_idx(u8 joker_id);
|
||||
static void s_joker_pb_add_sprite_user(int pb);
|
||||
static void s_joker_pb_remove_sprite_user(int pb);
|
||||
static int s_joker_pb_get_num_sprite_users(int joker_pb);
|
||||
static int s_get_unused_joker_pb(void);
|
||||
static int s_allocate_pb_if_needed(u8 joker_id);
|
||||
|
||||
void joker_init()
|
||||
{
|
||||
// This should init once only so no need to free
|
||||
int num_spritesheets = get_num_spritesheets();
|
||||
joker_gfxTiles = (const unsigned int**)malloc((sizeof(unsigned int*) * num_spritesheets));
|
||||
joker_gfxPal = (const unsigned short**)malloc((sizeof(unsigned int*) * num_spritesheets));
|
||||
joker_spritesheet_pb_map = (int*)malloc(sizeof(int) * num_spritesheets);
|
||||
|
||||
// TODO: Automate this with the preprocessor somehow?
|
||||
joker_gfxTiles[0] = joker_gfx0Tiles;
|
||||
joker_gfxTiles[1] = joker_gfx1Tiles;
|
||||
joker_gfxTiles[2] = joker_gfx2Tiles;
|
||||
joker_gfxTiles[3] = joker_gfx3Tiles;
|
||||
joker_gfxTiles[4] = joker_gfx4Tiles;
|
||||
joker_gfxTiles[5] = joker_gfx5Tiles;
|
||||
joker_gfxTiles[6] = joker_gfx6Tiles;
|
||||
joker_gfxTiles[7] = joker_gfx7Tiles;
|
||||
joker_gfxTiles[8] = joker_gfx8Tiles;
|
||||
joker_gfxTiles[9] = joker_gfx9Tiles;
|
||||
joker_gfxTiles[10] = joker_gfx10Tiles;
|
||||
joker_gfxTiles[11] = joker_gfx11Tiles;
|
||||
joker_gfxTiles[12] = joker_gfx12Tiles;
|
||||
joker_gfxTiles[13] = joker_gfx13Tiles;
|
||||
joker_gfxTiles[14] = joker_gfx14Tiles;
|
||||
|
||||
joker_gfxPal[0] = joker_gfx0Pal;
|
||||
joker_gfxPal[1] = joker_gfx1Pal;
|
||||
joker_gfxPal[2] = joker_gfx2Pal;
|
||||
joker_gfxPal[3] = joker_gfx3Pal;
|
||||
joker_gfxPal[4] = joker_gfx4Pal;
|
||||
joker_gfxPal[5] = joker_gfx5Pal;
|
||||
joker_gfxPal[6] = joker_gfx6Pal;
|
||||
joker_gfxPal[7] = joker_gfx7Pal;
|
||||
joker_gfxPal[8] = joker_gfx8Pal;
|
||||
joker_gfxPal[9] = joker_gfx9Pal;
|
||||
joker_gfxPal[10] = joker_gfx10Pal;
|
||||
joker_gfxPal[11] = joker_gfx11Pal;
|
||||
joker_gfxPal[12] = joker_gfx12Pal;
|
||||
joker_gfxPal[13] = joker_gfx13Pal;
|
||||
joker_gfxPal[14] = joker_gfx14Pal;
|
||||
int num_spritesheets = s_get_num_spritesheets();
|
||||
|
||||
for (int i = 0; i < num_spritesheets; i++)
|
||||
{
|
||||
joker_spritesheet_pb_map[i] = UNDEFINED;
|
||||
_joker_spritesheet_pb_map[i] = UNDEFINED;
|
||||
}
|
||||
}
|
||||
|
||||
Joker *joker_new(u8 id)
|
||||
Joker* joker_new(u8 id)
|
||||
{
|
||||
if (id >= get_joker_registry_size()) return NULL;
|
||||
if (id >= get_joker_registry_size())
|
||||
return NULL;
|
||||
|
||||
Joker *joker = (Joker*)malloc(sizeof(Joker));
|
||||
const JokerInfo *jinfo = get_joker_registry_entry(id);
|
||||
Joker* joker = POOL_GET(Joker);
|
||||
const JokerInfo* jinfo = get_joker_registry_entry(id);
|
||||
|
||||
joker->id = id;
|
||||
joker->modifier = BASE_EDITION; // TODO: Make this a parameter
|
||||
joker->value = jinfo->base_value + edition_price_lut[joker->modifier];
|
||||
joker->rarity = jinfo->rarity;
|
||||
joker->processed = false;
|
||||
joker->scoring_state = 0;
|
||||
joker->persistent_state = 0;
|
||||
|
||||
// initialize persistent Joker data if needed
|
||||
JokerEffect* joker_effect = NULL;
|
||||
jinfo->joker_effect_func(joker, NULL, JOKER_EVENT_ON_JOKER_CREATED, &joker_effect);
|
||||
|
||||
return joker;
|
||||
}
|
||||
|
||||
void joker_destroy(Joker **joker)
|
||||
void joker_destroy(Joker** joker)
|
||||
{
|
||||
if (*joker == NULL) return;
|
||||
free(*joker);
|
||||
POOL_FREE(Joker, *joker);
|
||||
*joker = NULL;
|
||||
}
|
||||
|
||||
JokerEffect joker_get_score_effect(Joker *joker, Card *scored_card)
|
||||
u32 joker_get_score_effect(
|
||||
Joker* joker,
|
||||
Card* scored_card,
|
||||
enum JokerEvent joker_event,
|
||||
JokerEffect** joker_effect
|
||||
)
|
||||
{
|
||||
const JokerInfo *jinfo = get_joker_registry_entry(joker->id);
|
||||
if (!jinfo) return (JokerEffect){0};
|
||||
const JokerInfo* jinfo = get_joker_registry_entry(joker->id);
|
||||
if (!jinfo)
|
||||
return JOKER_EFFECT_FLAG_NONE;
|
||||
|
||||
return jinfo->effect(joker, scored_card);
|
||||
return jinfo->joker_effect_func(joker, scored_card, joker_event, joker_effect);
|
||||
}
|
||||
|
||||
int joker_get_sell_value(const Joker* joker)
|
||||
|
|
@ -194,21 +120,21 @@ int joker_get_sell_value(const Joker* joker)
|
|||
return UNDEFINED;
|
||||
}
|
||||
|
||||
return joker->value/2;
|
||||
return joker->value / 2;
|
||||
}
|
||||
|
||||
// JokerObject methods
|
||||
JokerObject *joker_object_new(Joker *joker)
|
||||
JokerObject* joker_object_new(Joker* joker)
|
||||
{
|
||||
JokerObject *joker_object = malloc(sizeof(JokerObject));
|
||||
JokerObject* joker_object = POOL_GET(JokerObject);
|
||||
|
||||
int layer = 0;
|
||||
for (int i = 0; i < MAX_JOKER_OBJECTS; i++)
|
||||
{
|
||||
if (!used_layers[i])
|
||||
if (!_used_layers[i])
|
||||
{
|
||||
layer = i;
|
||||
used_layers[i] = true; // Mark this layer as used
|
||||
_used_layers[i] = true; // Mark this layer as used
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -217,137 +143,181 @@ JokerObject *joker_object_new(Joker *joker)
|
|||
joker_object->sprite_object = sprite_object_new();
|
||||
|
||||
int tile_index = JOKER_TID + (layer * JOKER_SPRITE_OFFSET);
|
||||
|
||||
int joker_spritesheet_idx = joker_get_spritesheet_idx(joker->id);
|
||||
|
||||
int joker_spritesheet_idx = s_joker_get_spritesheet_idx(joker->id);
|
||||
int joker_idx = joker->id % NUM_JOKERS_PER_SPRITESHEET;
|
||||
int joker_pb = allocate_pb_if_needed(joker->id);
|
||||
joker_pb_add_sprite_user(joker_pb);
|
||||
int joker_pb = s_allocate_pb_if_needed(joker->id);
|
||||
s_joker_pb_add_sprite_user(joker_pb);
|
||||
|
||||
memcpy32(&tile_mem[4][tile_index], &joker_gfxTiles[joker_spritesheet_idx][joker_idx * TILE_SIZE * JOKER_SPRITE_OFFSET], TILE_SIZE * JOKER_SPRITE_OFFSET);
|
||||
memcpy32(
|
||||
&tile_mem[TILE_MEM_OBJ_CHARBLOCK0_IDX][tile_index],
|
||||
&joker_gfxTiles[joker_spritesheet_idx][joker_idx * TILE_SIZE * JOKER_SPRITE_OFFSET],
|
||||
TILE_SIZE * JOKER_SPRITE_OFFSET
|
||||
);
|
||||
|
||||
sprite_object_set_sprite
|
||||
(
|
||||
joker_object->sprite_object,
|
||||
sprite_new
|
||||
(
|
||||
ATTR0_SQUARE | ATTR0_4BPP | ATTR0_AFF,
|
||||
ATTR1_SIZE_32,
|
||||
tile_index,
|
||||
sprite_object_set_sprite(
|
||||
joker_object->sprite_object,
|
||||
sprite_new(
|
||||
ATTR0_SQUARE | ATTR0_4BPP | ATTR0_AFF,
|
||||
ATTR1_SIZE_32,
|
||||
tile_index,
|
||||
joker_pb,
|
||||
JOKER_STARTING_LAYER + layer
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
|
||||
return joker_object;
|
||||
}
|
||||
|
||||
void joker_object_destroy(JokerObject **joker_object)
|
||||
void joker_object_destroy(JokerObject** joker_object)
|
||||
{
|
||||
if (joker_object == NULL || *joker_object == NULL) return;
|
||||
if (joker_object == NULL || *joker_object == NULL)
|
||||
return;
|
||||
|
||||
int layer = sprite_get_layer(joker_object_get_sprite(*joker_object)) - JOKER_STARTING_LAYER;
|
||||
used_layers[layer] = false;
|
||||
joker_pb_remove_sprite_user(sprite_get_pb(joker_object_get_sprite(*joker_object)));
|
||||
if (joker_pb_get_num_sprite_users((sprite_get_pb(joker_object_get_sprite(*joker_object)))) == 0)
|
||||
_used_layers[layer] = false;
|
||||
s_joker_pb_remove_sprite_user(sprite_get_pb(joker_object_get_sprite(*joker_object)));
|
||||
if (s_joker_pb_get_num_sprite_users((sprite_get_pb(joker_object_get_sprite(*joker_object)))) ==
|
||||
0)
|
||||
{
|
||||
joker_spritesheet_pb_map[joker_get_spritesheet_idx((*joker_object)->joker->id)] = UNDEFINED;
|
||||
_joker_spritesheet_pb_map[s_joker_get_spritesheet_idx((*joker_object)->joker->id)] =
|
||||
UNDEFINED;
|
||||
}
|
||||
|
||||
sprite_object_destroy(&(*joker_object)->sprite_object); // Destroy the sprite
|
||||
joker_destroy(&(*joker_object)->joker); // Destroy the joker
|
||||
free(*joker_object);
|
||||
joker_destroy(&(*joker_object)->joker); // Destroy the joker
|
||||
POOL_FREE(JokerObject, *joker_object);
|
||||
*joker_object = NULL;
|
||||
}
|
||||
|
||||
void joker_object_update(JokerObject *joker_object)
|
||||
void joker_object_update(JokerObject* joker_object)
|
||||
{
|
||||
CardObject *card_object = (CardObject *)joker_object;
|
||||
CardObject* card_object = (CardObject*)joker_object;
|
||||
card_object_update(card_object);
|
||||
}
|
||||
|
||||
void joker_object_shake(JokerObject *joker_object, mm_word sound_id)
|
||||
void joker_object_shake(JokerObject* joker_object, mm_word sound_id)
|
||||
{
|
||||
sprite_object_shake(joker_object->sprite_object, sound_id);
|
||||
}
|
||||
|
||||
bool joker_object_score(JokerObject *joker_object, Card* scored_card, int *chips, int *mult, int *xmult, int *money, bool *retrigger)
|
||||
void set_and_shift_text(char* str, int* cursor_pos_x, int* cursor_pos_y, int color_pb)
|
||||
{
|
||||
if (joker_object->joker->processed == true) return false; // If the joker has already been processed, return false
|
||||
tte_set_pos(*cursor_pos_x, *cursor_pos_y);
|
||||
tte_set_special(color_pb * TTE_SPECIAL_PB_MULT_OFFSET);
|
||||
tte_write(str);
|
||||
|
||||
JokerEffect joker_effect = joker_get_score_effect(joker_object->joker, scored_card);
|
||||
// + 1 For space
|
||||
const int joker_score_display_offset_px = (MAX_CARD_SCORE_STR_LEN + 1) * TTE_CHAR_SIZE;
|
||||
*cursor_pos_x += joker_score_display_offset_px;
|
||||
}
|
||||
|
||||
if (memcmp(&joker_effect, &(JokerEffect){0}, sizeof(JokerEffect)) != 0)
|
||||
bool joker_object_score(
|
||||
JokerObject* joker_object,
|
||||
CardObject* card_object,
|
||||
enum JokerEvent joker_event
|
||||
)
|
||||
{
|
||||
if (joker_object == NULL)
|
||||
{
|
||||
*chips += joker_effect.chips;
|
||||
*mult += joker_effect.mult;
|
||||
*mult *= joker_effect.xmult > 0 ? joker_effect.xmult : 1; // if xmult is zero, DO NOT multiply by it
|
||||
*money += joker_effect.money;
|
||||
// TODO: Retrigger
|
||||
|
||||
const int joker_score_display_offset_px = (MAX_CARD_SCORE_STR_LEN + 1)*TTE_CHAR_SIZE;
|
||||
// + 1 For space
|
||||
|
||||
int cursorPosX = fx2int(joker_object->sprite_object->x) + 8; // Offset of 16 pixels to center the text on the card
|
||||
if (joker_effect.chips > 0)
|
||||
{
|
||||
char score_buffer[INT_MAX_DIGITS + 2]; // For '+' and null terminator
|
||||
tte_set_pos(cursorPosX, JOKER_SCORE_TEXT_Y);
|
||||
tte_set_special(0xD000); // Blue
|
||||
snprintf(score_buffer, sizeof(score_buffer), "+%d", joker_effect.chips);
|
||||
tte_write(score_buffer);
|
||||
cursorPosX += joker_score_display_offset_px;
|
||||
}
|
||||
if (joker_effect.mult > 0)
|
||||
{
|
||||
char score_buffer[INT_MAX_DIGITS + 2];
|
||||
tte_set_pos(cursorPosX, JOKER_SCORE_TEXT_Y);
|
||||
tte_set_special(0xE000); // Red
|
||||
snprintf(score_buffer, sizeof(score_buffer), "+%d", joker_effect.mult);
|
||||
tte_write(score_buffer);
|
||||
cursorPosX += joker_score_display_offset_px;
|
||||
}
|
||||
if (joker_effect.xmult > 0)
|
||||
{
|
||||
char score_buffer[INT_MAX_DIGITS + 2];
|
||||
tte_set_pos(cursorPosX, JOKER_SCORE_TEXT_Y);
|
||||
tte_set_special(0xE000); // Red
|
||||
snprintf(score_buffer, sizeof(score_buffer), "X%d", joker_effect.xmult);
|
||||
tte_write(score_buffer);
|
||||
cursorPosX += joker_score_display_offset_px;
|
||||
}
|
||||
if (joker_effect.money > 0)
|
||||
{
|
||||
char score_buffer[INT_MAX_DIGITS + 2];
|
||||
tte_set_pos(cursorPosX, JOKER_SCORE_TEXT_Y);
|
||||
tte_set_special(0xC000); // Yellow
|
||||
snprintf(score_buffer, sizeof(score_buffer), "+%d", joker_effect.money);
|
||||
tte_write(score_buffer);
|
||||
cursorPosX += joker_score_display_offset_px;
|
||||
}
|
||||
|
||||
joker_object->joker->processed = true; // Mark the joker as processed
|
||||
joker_object_shake(joker_object, SFX_CARD_SELECT); // TODO: Add a sound effect for scoring the joker
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
JokerEffect* joker_effect = NULL;
|
||||
u32 effect_flags_ret =
|
||||
joker_get_score_effect(joker_object->joker, card_object->card, joker_event, &joker_effect);
|
||||
|
||||
void joker_object_set_selected(JokerObject* joker_object, bool selected)
|
||||
{
|
||||
if (joker_object == NULL)
|
||||
return;
|
||||
sprite_object_set_selected(joker_object->sprite_object, selected);
|
||||
}
|
||||
|
||||
bool joker_object_is_selected(JokerObject* joker_object)
|
||||
{
|
||||
if (joker_object == NULL)
|
||||
if (effect_flags_ret == JOKER_EFFECT_FLAG_NONE)
|
||||
{
|
||||
return false;
|
||||
return sprite_object_is_selected(joker_object->sprite_object);
|
||||
}
|
||||
|
||||
u32 chips = get_chips();
|
||||
u32 mult = get_mult();
|
||||
int money = get_money();
|
||||
|
||||
if (effect_flags_ret & JOKER_EFFECT_FLAG_RETRIGGER)
|
||||
{
|
||||
set_retrigger(joker_effect->retrigger);
|
||||
}
|
||||
|
||||
// joker_effect.message will have been set if the Joker had anything custom to say
|
||||
|
||||
int cursorPosX = TILE_SIZE; // Offset of one tile to better center the text on the card
|
||||
int cursorPosY = 0;
|
||||
if (joker_event == JOKER_EVENT_ON_CARD_HELD)
|
||||
{
|
||||
// display the text on top of the card instead of below the Joker for Held Cards effects
|
||||
// scored_card cannot be NULL here because of the joker event
|
||||
cursorPosX += fx2int(card_object->sprite_object->x);
|
||||
cursorPosY = HELD_CARD_SCORE_TEXT_Y;
|
||||
}
|
||||
else
|
||||
{
|
||||
cursorPosX += fx2int(joker_object->sprite_object->x);
|
||||
cursorPosY = JOKER_SCORE_TEXT_Y;
|
||||
}
|
||||
|
||||
mm_word sfx_id;
|
||||
if (effect_flags_ret & JOKER_EFFECT_FLAG_CHIPS)
|
||||
{
|
||||
chips = u32_protected_add(chips, joker_effect->chips);
|
||||
char score_buffer[INT_MAX_DIGITS + 2]; // For '+' and null terminator
|
||||
snprintf(score_buffer, sizeof(score_buffer), "+%lu", joker_effect->chips);
|
||||
set_and_shift_text(score_buffer, &cursorPosX, &cursorPosY, TTE_BLUE_PB);
|
||||
sfx_id = SFX_CHIPS_GENERIC; // The joker chips effect is "generic"
|
||||
}
|
||||
if (effect_flags_ret & JOKER_EFFECT_FLAG_MULT)
|
||||
{
|
||||
mult = u32_protected_add(mult, joker_effect->mult);
|
||||
char score_buffer[INT_MAX_DIGITS + 2];
|
||||
snprintf(score_buffer, sizeof(score_buffer), "+%lu", joker_effect->mult);
|
||||
set_and_shift_text(score_buffer, &cursorPosX, &cursorPosY, TTE_RED_PB);
|
||||
sfx_id = SFX_MULT;
|
||||
}
|
||||
// if xmult is zero, DO NOT multiply by it
|
||||
if (effect_flags_ret & JOKER_EFFECT_FLAG_XMULT && joker_effect->xmult > 0)
|
||||
{
|
||||
mult = u32_protected_mult(mult, joker_effect->xmult);
|
||||
char score_buffer[INT_MAX_DIGITS + 2];
|
||||
snprintf(score_buffer, sizeof(score_buffer), "X%lu", joker_effect->xmult);
|
||||
set_and_shift_text(score_buffer, &cursorPosX, &cursorPosY, TTE_RED_PB);
|
||||
sfx_id = SFX_XMULT;
|
||||
}
|
||||
if (effect_flags_ret & JOKER_EFFECT_FLAG_MONEY)
|
||||
{
|
||||
money += joker_effect->money;
|
||||
char score_buffer[INT_MAX_DIGITS + 2];
|
||||
snprintf(score_buffer, sizeof(score_buffer), "%d$", joker_effect->money);
|
||||
set_and_shift_text(score_buffer, &cursorPosX, &cursorPosY, TTE_YELLOW_PB);
|
||||
// TODO: Money sound effect
|
||||
}
|
||||
// custom message for Jokers (including retriggers where Jokers will say "Again!")
|
||||
// joker_effect->message will have been set if the Joker had anything custom to say
|
||||
if (effect_flags_ret & JOKER_EFFECT_FLAG_MESSAGE)
|
||||
{
|
||||
set_and_shift_text(joker_effect->message, &cursorPosX, &cursorPosY, TTE_WHITE_PB);
|
||||
}
|
||||
// this will start the Joker expire animation
|
||||
if (effect_flags_ret & JOKER_EFFECT_FLAG_EXPIRE && joker_effect->expire)
|
||||
{
|
||||
joker_object_shake(joker_object, UNDEFINED);
|
||||
list_push_back(get_expired_jokers_list(), joker_object);
|
||||
}
|
||||
|
||||
// Update values
|
||||
set_chips(chips);
|
||||
set_mult(mult);
|
||||
set_money(money);
|
||||
|
||||
// Update displays
|
||||
display_chips();
|
||||
display_mult();
|
||||
display_money();
|
||||
|
||||
joker_object_shake(joker_object, sfx_id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Sprite* joker_object_get_sprite(JokerObject* joker_object)
|
||||
|
|
@ -356,3 +326,99 @@ Sprite* joker_object_get_sprite(JokerObject* joker_object)
|
|||
return NULL;
|
||||
return sprite_object_get_sprite(joker_object->sprite_object);
|
||||
}
|
||||
|
||||
int joker_get_random_rarity()
|
||||
{
|
||||
int joker_rarity = 0;
|
||||
int rarity_roll = random() % 100;
|
||||
if (rarity_roll < COMMON_JOKER_CHANCE)
|
||||
{
|
||||
joker_rarity = COMMON_JOKER;
|
||||
}
|
||||
else if (rarity_roll < COMMON_JOKER_CHANCE + UNCOMMON_JOKER_CHANCE)
|
||||
{
|
||||
joker_rarity = UNCOMMON_JOKER;
|
||||
}
|
||||
else if (rarity_roll < COMMON_JOKER_CHANCE + UNCOMMON_JOKER_CHANCE + RARE_JOKER_CHANCE)
|
||||
{
|
||||
joker_rarity = RARE_JOKER;
|
||||
}
|
||||
else if (rarity_roll < COMMON_JOKER_CHANCE + UNCOMMON_JOKER_CHANCE + RARE_JOKER_CHANCE +
|
||||
LEGENDARY_JOKER_CHANCE)
|
||||
{
|
||||
joker_rarity = LEGENDARY_JOKER;
|
||||
}
|
||||
|
||||
return joker_rarity;
|
||||
}
|
||||
|
||||
static int s_get_num_spritesheets()
|
||||
{
|
||||
return (get_joker_registry_size() + NUM_JOKERS_PER_SPRITESHEET - 1) /
|
||||
NUM_JOKERS_PER_SPRITESHEET;
|
||||
}
|
||||
|
||||
static int s_joker_get_spritesheet_idx(u8 joker_id)
|
||||
{
|
||||
return joker_id / NUM_JOKERS_PER_SPRITESHEET;
|
||||
}
|
||||
|
||||
static void s_joker_pb_add_sprite_user(int pb)
|
||||
{
|
||||
_joker_pb_num_sprite_users[pb - JOKER_BASE_PB]++;
|
||||
}
|
||||
|
||||
static void s_joker_pb_remove_sprite_user(int pb)
|
||||
{
|
||||
int num_sprite_users = _joker_pb_num_sprite_users[pb - JOKER_BASE_PB];
|
||||
_joker_pb_num_sprite_users[pb - JOKER_BASE_PB] = max(0, num_sprite_users - 1);
|
||||
}
|
||||
|
||||
static int s_joker_pb_get_num_sprite_users(int joker_pb)
|
||||
{
|
||||
return _joker_pb_num_sprite_users[joker_pb - JOKER_BASE_PB];
|
||||
}
|
||||
|
||||
static int s_get_unused_joker_pb()
|
||||
{
|
||||
for (int i = 0; i < NUM_ELEM_IN_ARR(_joker_pb_num_sprite_users); i++)
|
||||
{
|
||||
if (_joker_pb_num_sprite_users[i] == 0)
|
||||
{
|
||||
return (i + JOKER_BASE_PB);
|
||||
}
|
||||
}
|
||||
|
||||
return UNDEFINED;
|
||||
}
|
||||
|
||||
static int s_allocate_pb_if_needed(u8 joker_id)
|
||||
{
|
||||
int joker_spritesheet_idx = s_joker_get_spritesheet_idx(joker_id);
|
||||
int joker_pb = _joker_spritesheet_pb_map[joker_spritesheet_idx];
|
||||
if (joker_pb != UNDEFINED)
|
||||
{
|
||||
// Already allocated
|
||||
return joker_pb;
|
||||
}
|
||||
|
||||
// Allocate a new palette
|
||||
joker_pb = s_get_unused_joker_pb();
|
||||
|
||||
if (joker_pb == UNDEFINED)
|
||||
{
|
||||
// Ran out of palettes, default to base and pray
|
||||
joker_pb = JOKER_BASE_PB;
|
||||
}
|
||||
else
|
||||
{
|
||||
_joker_spritesheet_pb_map[joker_spritesheet_idx] = joker_pb;
|
||||
memcpy16(
|
||||
&pal_obj_mem[PAL_ROW_LEN * joker_pb],
|
||||
joker_gfxPal[joker_spritesheet_idx],
|
||||
NUM_ELEM_IN_ARR(joker_gfx0Pal)
|
||||
);
|
||||
}
|
||||
|
||||
return joker_pb;
|
||||
}
|
||||
|
|
|
|||