mirror of
https://github.com/FunkinCrew/Funkin.git
synced 2025-04-21 19:31:52 -04:00
Merge branch 'develop' into bugfix/editor-tooltips
This commit is contained in:
commit
17a553f75a
220 changed files with 6344 additions and 3687 deletions
.github
ISSUE_TEMPLATE
actions/setup-haxe
workflows
.vscode
CHANGELOG.mdartassetsbuild
docs
hmm.jsonproject.hxpsource
Prebuild.hx
funkin
InitState.hxPlayerSettings.hxPreferences.hx
api
discord
newgrounds
audio
data
animation
dialogue
event
freeplay/player
notestyle
song
stage
story/level
effects
graphics
FlxFilteredSprite.hxFunkinSprite.hx
adobeanimate
shaders
BlueFade.hxDropShadowScreenspace.hxDropShadowShader.hxHSVShader.hxRuntimeRainShader.hxTextureSwap.hxTitleOutline.hx
video
input
modding
play
Countdown.hxGameOverSubState.hxGitarooPause.hxPauseSubState.hxPlayState.hxResultState.hx
character
AnimateAtlasCharacter.hxBaseCharacter.hxCharacterData.hxMultiSparrowCharacter.hxPackerCharacter.hxSparrowCharacter.hx
components
cutscene
event
FocusCameraSongEvent.hxPlayAnimationSongEvent.hxScriptedSongEvent.hxScrollSpeedEvent.hxSetCameraBopSongEvent.hxSetCharacterSongEvent.hxSetHealthIconSongEvent.hxSetStageSongEvent.hxZoomCameraSongEvent.hx
notes
10
.github/ISSUE_TEMPLATE/bug.yml
vendored
10
.github/ISSUE_TEMPLATE/bug.yml
vendored
|
@ -6,7 +6,7 @@ body:
|
|||
- type: markdown
|
||||
attributes:
|
||||
value: "# PLEASE READ THE [CONTRIBUTING GUIDE](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md) BEFORE OPENING ISSUES!"
|
||||
|
||||
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Issue Checklist
|
||||
|
@ -45,11 +45,11 @@ body:
|
|||
- type: input
|
||||
attributes:
|
||||
label: Version
|
||||
description: Which version are you playing on? The game version is in the bottom left corner of the main menu.
|
||||
description: Which version are you playing on? The game version is in the bottom left corner of the main menu.
|
||||
placeholder: ex. 0.5.3
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "## Describe your bug."
|
||||
|
@ -61,11 +61,11 @@ body:
|
|||
- type: textarea
|
||||
attributes:
|
||||
label: Description (include any images, videos, errors, or crash logs)
|
||||
description: Provide as much detail as you can. The better others understand your issue, the more they can help you!
|
||||
description: Provide as much detail as you can. The better others understand your issue, the more they can help you!
|
||||
placeholder: Describe your issue here...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to Reproduce
|
||||
|
|
4
.github/actions/setup-haxe/action.yml
vendored
4
.github/actions/setup-haxe/action.yml
vendored
|
@ -5,7 +5,7 @@ inputs:
|
|||
haxe:
|
||||
description: 'Version of haxe to install'
|
||||
required: true
|
||||
default: '4.3.4'
|
||||
default: '4.3.6'
|
||||
hxcpp-cache:
|
||||
description: 'Whether to use a shared hxcpp compile cache'
|
||||
required: true
|
||||
|
@ -30,7 +30,7 @@ runs:
|
|||
echo "TIMER_HAXE=$(date +%s)" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Install Haxe
|
||||
uses: funkincrew/ci-haxe@v3.1.0
|
||||
uses: funkincrew/ci-haxe@v3.2.6
|
||||
with:
|
||||
haxe-version: ${{ inputs.haxe }}
|
||||
|
||||
|
|
53
.github/workflows/build-docker-image.yml
vendored
53
.github/workflows/build-docker-image.yml
vendored
|
@ -1,53 +0,0 @@
|
|||
name: Create and publish Docker image
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- '**/Dockerfile'
|
||||
- '.github/workflows/build-docker-image.yml'
|
||||
|
||||
jobs:
|
||||
build-and-push-image:
|
||||
runs-on: build-set
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Get checkout token
|
||||
uses: actions/create-github-app-token@v1
|
||||
id: app_token
|
||||
with:
|
||||
app-id: ${{ vars.APP_ID }}
|
||||
private-key: ${{ secrets.APP_PEM }}
|
||||
owner: ${{ github.repository_owner }}
|
||||
|
||||
- name: Checkout repo
|
||||
uses: funkincrew/ci-checkout@v6
|
||||
with:
|
||||
submodules: false
|
||||
token: ${{ steps.app_token.outputs.token }}
|
||||
|
||||
- name: Log into GitHub Container Registry
|
||||
uses: docker/login-action@v3.1.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5.3.0
|
||||
with:
|
||||
context: ./build
|
||||
push: true
|
||||
tags: |
|
||||
ghcr.io/funkincrew/build-dependencies:latest
|
||||
ghcr.io/funkincrew/build-dependencies:${{ github.sha }}
|
||||
labels: |
|
||||
org.opencontainers.image.description=precooked haxe build-dependencies
|
||||
org.opencontainers.image.revision=${{ github.sha }}
|
||||
org.opencontainers.image.source=https://github.com/${{ github.repository }}
|
||||
org.opencontainers.image.title=${{ github.repository_owner }}/build-dependencies
|
||||
org.opencontainers.image.url=https://github.com/${{ github.repository }}
|
||||
org.opencontainers.image.version=${{ github.sha }}
|
334
.github/workflows/build-game.yml
vendored
334
.github/workflows/build-game.yml
vendored
|
@ -12,146 +12,238 @@ on:
|
|||
description: Save the build artifact to Github Actions (sends to itch otherwise)
|
||||
default: false
|
||||
push:
|
||||
paths-ignore:
|
||||
- '**/Dockerfile'
|
||||
- '.github/workflows/build-docker-image.yml'
|
||||
|
||||
jobs:
|
||||
|
||||
gather-changes:
|
||||
runs-on: build-set
|
||||
outputs:
|
||||
trigger-build: ${{ steps.should-trigger.outputs.result }}
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: funkincrew/ci-checkout@v6
|
||||
with:
|
||||
submodules: false
|
||||
- uses: dorny/paths-filter@v3
|
||||
id: filter
|
||||
with:
|
||||
base: ${{ github.ref }}
|
||||
filters: |
|
||||
docker:
|
||||
- '.github/workflows/build-game.yml'
|
||||
- '**/Dockerfile'
|
||||
- uses: actions/github-script@v7
|
||||
id: should-trigger
|
||||
with:
|
||||
result-encoding: string
|
||||
script: |
|
||||
const { payload } = context
|
||||
const changes = ${{ steps.filter.outputs.changes }}
|
||||
const manual = payload.commits
|
||||
.some(c => c.message.toLowerCase().replace(/\W/g, " ").includes("docker rebuild"))
|
||||
|
||||
console.log({ payload, changes, manual, commits: payload.commits })
|
||||
|
||||
return payload.created
|
||||
|| payload.forced
|
||||
|| changes.includes("docker")
|
||||
|| manual
|
||||
|
||||
gather-tags:
|
||||
runs-on: build-set
|
||||
outputs:
|
||||
primary: ${{ steps.build-tags.outputs.primary }}
|
||||
list: ${{ steps.build-tags.outputs.list }}
|
||||
steps:
|
||||
- name: Gather build tags
|
||||
uses: actions/github-script@v7
|
||||
id: build-tags
|
||||
with:
|
||||
script: |
|
||||
const base = "ghcr.io/funkincrew/build-dependencies"
|
||||
const default_branch = "rewrite/master"
|
||||
|
||||
const ref_path = context.ref.split("/").slice(2)
|
||||
const ref = ref_path.join("/")
|
||||
const ref_tag = ref_path.join("-")
|
||||
|
||||
const tags = [ref_tag, context.sha]
|
||||
|
||||
if (ref === default_branch) tags.push("latest")
|
||||
|
||||
console.log([
|
||||
`ref: ${ref}`,
|
||||
`default_branch: ${default_branch}`,
|
||||
`tags: ${tags.join(", ")}`
|
||||
].join('\n'))
|
||||
|
||||
const tag_list = tags
|
||||
.map(tag => `${base}:${tag}`)
|
||||
.join("\n")
|
||||
|
||||
core.setOutput("primary", ref_tag)
|
||||
core.setOutput("list", tag_list)
|
||||
|
||||
docker-image:
|
||||
needs: [gather-changes, gather-tags]
|
||||
if: needs.gather-changes.outputs.trigger-build == 'true'
|
||||
runs-on: build-set
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: funkincrew/ci-checkout@v6
|
||||
with:
|
||||
submodules: false
|
||||
- name: Log into GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: ./build
|
||||
push: true
|
||||
tags: ${{ needs.gather-tags.outputs.list }}
|
||||
labels: |
|
||||
org.opencontainers.image.description=precooked haxe build dependencies
|
||||
org.opencontainers.image.revision=${{ github.sha }}
|
||||
org.opencontainers.image.source=https://github.com/${{ github.repository }}
|
||||
org.opencontainers.image.title=${{ github.repository_owner }}/build-dependencies
|
||||
org.opencontainers.image.url=https://github.com/${{ github.repository }}
|
||||
org.opencontainers.image.version=${{ github.sha }}
|
||||
|
||||
build-game-on-host:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- target: windows
|
||||
- target: macos
|
||||
- target: windows
|
||||
runs-on: windows-latest
|
||||
- target: macos
|
||||
runs-on: macos
|
||||
runs-on:
|
||||
- ${{ matrix.target }}
|
||||
- ${{ matrix.runs-on }}
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
BUILD_DEFINES: ${{ github.event.inputs.build-defines || '-DGITHUB_BUILD' }}
|
||||
steps:
|
||||
- name: Make git happy
|
||||
run: |
|
||||
git config --global --replace-all safe.directory $GITHUB_WORKSPACE
|
||||
|
||||
- name: Get checkout token
|
||||
uses: actions/create-github-app-token@v1
|
||||
id: app_token
|
||||
with:
|
||||
app-id: ${{ vars.APP_ID }}
|
||||
private-key: ${{ secrets.APP_PEM }}
|
||||
owner: ${{ github.repository_owner }}
|
||||
|
||||
- name: Checkout repo
|
||||
uses: funkincrew/ci-checkout@v6
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
token: ${{ steps.app_token.outputs.token }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup build environment
|
||||
uses: ./.github/actions/setup-haxe
|
||||
with:
|
||||
gh-token: ${{ steps.app_token.outputs.token }}
|
||||
- name: Setup HXCPP dev commit
|
||||
run: |
|
||||
cd .haxelib/hxcpp/git/tools/hxcpp
|
||||
haxe compile.hxml
|
||||
cd ../../../../..
|
||||
- name: Build game
|
||||
if: ${{ matrix.target == 'windows' }}
|
||||
run: |
|
||||
haxelib run lime build windows -v -release ${{ github.event.inputs.build-defines }}
|
||||
timeout-minutes: 120
|
||||
- name: Build game
|
||||
if: ${{ matrix.target != 'windows' }}
|
||||
run: |
|
||||
haxelib run lime build ${{ matrix.target }} -v -release --times ${{ github.event.inputs.build-defines }}
|
||||
timeout-minutes: 120
|
||||
- name: Save build artifact to Github Actions
|
||||
if: ${{ github.event.inputs.save-artifact }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-${{ matrix.target }}
|
||||
path: export/release/${{matrix.target}}/bin/
|
||||
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: ./.github/actions/upload-itch
|
||||
with:
|
||||
butler-key: ${{ secrets.BUTLER_API_KEY}}
|
||||
target: ${{ matrix.target }}
|
||||
- name: Make git happy
|
||||
run: |
|
||||
git config --global --replace-all safe.directory $GITHUB_WORKSPACE
|
||||
- name: Get checkout token
|
||||
uses: actions/create-github-app-token@v1
|
||||
id: app_token
|
||||
with:
|
||||
app-id: ${{ vars.APP_ID }}
|
||||
private-key: ${{ secrets.APP_PEM }}
|
||||
owner: ${{ github.repository_owner }}
|
||||
- name: Checkout repo
|
||||
uses: funkincrew/ci-checkout@v6
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
token: ${{ steps.app_token.outputs.token }}
|
||||
persist-credentials: false
|
||||
- name: Setup build environment
|
||||
uses: ./.github/actions/setup-haxe
|
||||
with:
|
||||
gh-token: ${{ steps.app_token.outputs.token }}
|
||||
- name: Setup HXCPP dev commit
|
||||
run: |
|
||||
cd .haxelib/hxcpp/git/tools/hxcpp
|
||||
haxe compile.hxml
|
||||
cd ../../../../..
|
||||
- name: Build game (windows)
|
||||
if: ${{ matrix.target == 'windows' }}
|
||||
run: |
|
||||
haxelib run lime build windows -v -release $BUILD_DEFINES
|
||||
timeout-minutes: 120
|
||||
- name: Build game (unix)
|
||||
if: ${{ matrix.target != 'windows' }}
|
||||
run: |
|
||||
haxelib run lime build ${{ matrix.target }} -v -release --times $BUILD_DEFINES
|
||||
timeout-minutes: 120
|
||||
- name: Save build artifact to Github Actions
|
||||
if: ${{ github.event.inputs.save-artifact }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-${{ matrix.target }}
|
||||
path: export/release/${{matrix.target}}/bin/
|
||||
- name: Upload build artifacts
|
||||
uses: ./.github/actions/upload-itch
|
||||
with:
|
||||
butler-key: ${{ secrets.BUTLER_API_KEY}}
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
build-game-in-container:
|
||||
needs: [gather-tags, docker-image]
|
||||
if: ${{ ! cancelled() }}
|
||||
runs-on: build-set
|
||||
container: ghcr.io/funkincrew/build-dependencies:latest
|
||||
container: ghcr.io/funkincrew/build-dependencies:${{ needs.gather-tags.outputs.primary }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- target: linux
|
||||
- target: html5
|
||||
- target: linux
|
||||
- target: html5
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
BUILD_DEFINES: ${{ github.event.inputs.build-defines || '-DGITHUB_BUILD' }}
|
||||
steps:
|
||||
- name: Get checkout token
|
||||
uses: actions/create-github-app-token@v1
|
||||
id: app_token
|
||||
with:
|
||||
app-id: ${{ vars.APP_ID }}
|
||||
private-key: ${{ secrets.APP_PEM }}
|
||||
owner: ${{ github.repository_owner }}
|
||||
|
||||
- name: Checkout repo
|
||||
uses: funkincrew/ci-checkout@v6
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
token: ${{ steps.app_token.outputs.token }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Config haxelib
|
||||
run: |
|
||||
haxelib --never newrepo
|
||||
echo "HAXEPATH=$(haxelib config)" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Restore cached dependencies
|
||||
id: cache-hmm
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .haxelib
|
||||
key: haxe-hmm-${{ runner.os }}-${{ hashFiles('**/hmm.json') }}
|
||||
|
||||
- if: ${{ steps.cache-hmm.outputs.cache-hit != 'true' }}
|
||||
name: Install dependencies
|
||||
run: |
|
||||
git config --global 'url.https://x-access-token:${{ steps.app_token.outputs.token }}@github.com/.insteadOf' https://github.com/
|
||||
git config --global advice.detachedHead false
|
||||
haxelib --global run hmm install -q
|
||||
cd .haxelib/hxcpp/git/tools/hxcpp && haxe compile.hxml
|
||||
|
||||
- if: ${{ matrix.target != 'html5' }}
|
||||
name: Restore hxcpp cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: /usr/share/hxcpp
|
||||
key: haxe-hxcpp-${{ runner.os }}-${{ github.ref_name }}-${{ github.sha }}
|
||||
restore-keys: haxe-hxcpp-${{ runner.os }}-${{ github.ref_name }}
|
||||
|
||||
- name: Build game
|
||||
run: |
|
||||
haxelib run lime build ${{ matrix.target }} -v -release --times ${{ github.event.inputs.build-defines }}
|
||||
timeout-minutes: 120
|
||||
|
||||
- name: Save build artifact to Github Actions
|
||||
if: ${{ github.event.inputs.save-artifact }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-${{ matrix.target }}
|
||||
path: export/release/${{matrix.target}}/bin/
|
||||
- name: Upload build artifacts
|
||||
uses: ./.github/actions/upload-itch
|
||||
with:
|
||||
butler-key: ${{ secrets.BUTLER_API_KEY}}
|
||||
target: ${{ matrix.target }}
|
||||
- name: Get checkout token
|
||||
uses: actions/create-github-app-token@v1
|
||||
id: app_token
|
||||
with:
|
||||
app-id: ${{ vars.APP_ID }}
|
||||
private-key: ${{ secrets.APP_PEM }}
|
||||
owner: ${{ github.repository_owner }}
|
||||
- name: Checkout repo
|
||||
uses: funkincrew/ci-checkout@v6
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
token: ${{ steps.app_token.outputs.token }}
|
||||
persist-credentials: false
|
||||
- name: Config haxelib
|
||||
run: |
|
||||
haxelib --never newrepo
|
||||
echo "HAXEPATH=$(haxelib config)" >> "$GITHUB_ENV"
|
||||
- name: Restore cached dependencies
|
||||
id: cache-hmm
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .haxelib
|
||||
key: haxe-hmm-${{ runner.os }}-${{ hashFiles('**/hmm.json') }}
|
||||
- if: ${{ steps.cache-hmm.outputs.cache-hit != 'true' }}
|
||||
name: Install dependencies
|
||||
run: |
|
||||
git config --global 'url.https://x-access-token:${{ steps.app_token.outputs.token }}@github.com/.insteadOf' https://github.com/
|
||||
git config --global advice.detachedHead false
|
||||
haxelib --global run hmm install -q
|
||||
cd .haxelib/hxcpp/git/tools/hxcpp && haxe compile.hxml
|
||||
- if: ${{ matrix.target != 'html5' }}
|
||||
name: Restore hxcpp cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: /usr/share/hxcpp
|
||||
key: haxe-hxcpp-${{ runner.os }}-${{ github.ref_name }}-${{ github.sha }}
|
||||
restore-keys: haxe-hxcpp-${{ runner.os }}-${{ github.ref_name }}
|
||||
- name: Build game
|
||||
run: |
|
||||
haxelib run lime build ${{ matrix.target }} -v -release --times $BUILD_DEFINES
|
||||
timeout-minutes: 120
|
||||
- name: Save build artifact to Github Actions
|
||||
if: ${{ github.event.inputs.save-artifact }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-${{ matrix.target }}
|
||||
path: export/release/${{matrix.target}}/bin/
|
||||
- name: Upload build artifacts
|
||||
uses: ./.github/actions/upload-itch
|
||||
with:
|
||||
butler-key: ${{ secrets.BUTLER_API_KEY}}
|
||||
target: ${{ matrix.target }}
|
||||
|
|
22
.github/workflows/label-issue.yml
vendored
22
.github/workflows/label-issue.yml
vendored
|
@ -1,22 +0,0 @@
|
|||
name: "Issue Labeler"
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- edited
|
||||
|
||||
jobs:
|
||||
# When an issue is opened, detect if it has an empty body or incomplete issue form.
|
||||
# If it does, close the issue immediately.
|
||||
empty-issues:
|
||||
name: Close empty issues
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Run empty issues closer action
|
||||
uses: rickstaa/empty-issues-closer-action@v1
|
||||
env:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
close_comment: Closing this issue because it appears to be empty. Please update the issue for it to be reopened.
|
||||
open_comment: Reopening this issue because the author provided more information.
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -8,6 +8,7 @@ RECOVER_*.fla
|
|||
shitAudio/
|
||||
.build_time
|
||||
.swp
|
||||
NewgroundsCredentials.hx
|
||||
|
||||
# Exclude JS stuff
|
||||
node_modules/
|
||||
|
|
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
|
@ -6,7 +6,6 @@
|
|||
"vshaxe.hxcpp-debugger", // CPP debugging
|
||||
"openfl.lime-vscode-extension", // Lime integration
|
||||
"esbenp.prettier-vscode", // JSON formatting
|
||||
"redhat.vscode-xml", // XML formatting
|
||||
"ryanluker.vscode-coverage-gutters" // Highlight code coverage
|
||||
"redhat.vscode-xml" // XML formatting
|
||||
]
|
||||
}
|
||||
|
|
65
.vscode/settings.json
vendored
65
.vscode/settings.json
vendored
|
@ -70,7 +70,7 @@
|
|||
"files.eol": "\n",
|
||||
|
||||
"haxe.displayPort": "auto",
|
||||
"haxe.enableCompilationServer": false,
|
||||
"haxe.enableCompilationServer": true,
|
||||
"haxe.enableServerView": true,
|
||||
"haxe.displayServer": {
|
||||
"arguments": ["-v"]
|
||||
|
@ -91,26 +91,6 @@
|
|||
"haxecheckstyle.codeSimilarityBufferSize": 100,
|
||||
|
||||
"lime.targetConfigurations": [
|
||||
{
|
||||
"label": "Windows / Debug",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Debug (Tracy)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DFEATURE_DEBUG_TRACY", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "Linux / Debug",
|
||||
"target": "linux",
|
||||
"args": ["-debug", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "HashLink / Debug",
|
||||
"target": "hl",
|
||||
"args": ["-debug"]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Debug (Discord)",
|
||||
"target": "windows",
|
||||
|
@ -170,23 +150,13 @@
|
|||
"target": "windows",
|
||||
"args": ["-debug", "-DRESULTS"]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Debug (Straight to Chart Editor)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DCHARTING", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "HashLink / Debug (Straight to Chart Editor)",
|
||||
"target": "hl",
|
||||
"args": ["-debug", "-DCHARTING"]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Debug (Straight to Animation Editor)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DANIMDEBUG", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Debug (Debug hxCodec)",
|
||||
"label": "Windows / Debug (Debug hxvlc)",
|
||||
"target": "windows",
|
||||
"args": ["-debug", "-DHXC_LIBVLC_LOGGING", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
|
@ -210,11 +180,6 @@
|
|||
"target": "windows",
|
||||
"args": ["-debug", "-DWAVEFORM", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Release",
|
||||
"target": "windows",
|
||||
"args": ["-release"]
|
||||
},
|
||||
{
|
||||
"label": "Windows / Release (GitHub Actions)",
|
||||
"target": "windows",
|
||||
|
@ -225,29 +190,31 @@
|
|||
"target": "hl",
|
||||
"args": ["-debug", "-DWAVEFORM"]
|
||||
},
|
||||
{
|
||||
"label": "HTML5 / Debug",
|
||||
"target": "html5",
|
||||
"args": ["-debug", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "HTML5 / Debug (Watch)",
|
||||
"target": "html5",
|
||||
"args": ["-debug", "-watch", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
}
|
||||
],
|
||||
"lime.buildTypes": [
|
||||
{
|
||||
"label": "macOS / Debug",
|
||||
"target": "mac",
|
||||
"label": "Debug",
|
||||
"args": ["-debug", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "macOS / Release",
|
||||
"target": "mac",
|
||||
"label": "Debug (Tracy)",
|
||||
"args": ["-debug", "-DFEATURE_DEBUG_TRACY", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "Debug (Straight to Chart Editor)",
|
||||
"args": ["-debug", "-DCHARTING", "-DFEATURE_DEBUG_FUNCTIONS"]
|
||||
},
|
||||
{
|
||||
"label": "Release",
|
||||
"args": ["-release"]
|
||||
},
|
||||
{
|
||||
"label": "macOS / Release (GitHub Actions)",
|
||||
"target": "mac",
|
||||
"label": "Release (GitHub Actions)",
|
||||
"args": ["-release", "-DGITHUB_BUILD"]
|
||||
}
|
||||
],
|
||||
|
|
712
CHANGELOG.md
712
CHANGELOG.md
File diff suppressed because it is too large
Load diff
2
art
2
art
|
@ -1 +1 @@
|
|||
Subproject commit fbd3e3df77734606d88516770b71b56e6fa04bce
|
||||
Subproject commit 78dc310219370144719b4eeef9b3b511c5a44532
|
2
assets
2
assets
|
@ -1 +1 @@
|
|||
Subproject commit c1899ffbefb9a7c98b030c75a33623431d7ea6ba
|
||||
Subproject commit 0303a03f43b078d5263b541de1ff504fd7cfcde5
|
|
@ -1,8 +1,8 @@
|
|||
FROM ubuntu:mantic
|
||||
FROM ubuntu:noble
|
||||
|
||||
ARG haxe_version=4.3.4
|
||||
ARG haxe_version=4.3.6
|
||||
ARG haxelib_version=4.1.0
|
||||
ARG neko_version=2.3.0
|
||||
ARG neko_version=2.4.0
|
||||
|
||||
# prepare runner
|
||||
ENV GITHUB_HOME="/github/home"
|
||||
|
@ -98,11 +98,12 @@ EOF
|
|||
# neko
|
||||
# https://github.com/HaxeFoundation/neko/releases/download/v2-3-0/neko-2.3.0-linux64.tar.gz
|
||||
RUN <<EOF
|
||||
neko_url=$(curl https://api.github.com/repos/HaxeFoundation/neko/releases -sfL \
|
||||
| jq '.[] | select(.name == "'"$neko_version"'")' \
|
||||
| jq '.assets[] | select(.name | endswith("linux64.tar.gz"))' \
|
||||
| jq -r '.browser_download_url')
|
||||
curl -sfL "$neko_url" | tar -xz -C /usr/local
|
||||
#neko_url=$(curl https://api.github.com/repos/HaxeFoundation/neko/releases -fL \
|
||||
# | jq '.[] | select(.name == "'"$neko_version"'")' \
|
||||
# | jq '.assets[] | select(.name | endswith("linux64.tar.gz"))' \
|
||||
# | jq -r '.browser_download_url')
|
||||
neko_url="https://geo.thei.rs/funkin/neko-2.4.0-linux64.tar.gz"
|
||||
curl -fL "$neko_url" | tar -xz -C /usr/local
|
||||
EOF
|
||||
|
||||
RUN <<EOF
|
||||
|
@ -117,11 +118,12 @@ ENV PATH="$NEKOPATH:$PATH"
|
|||
# haxe
|
||||
# https://github.com/HaxeFoundation/haxe/releases/download/4.0.5/haxe-4.0.5-linux64.tar.gz
|
||||
RUN <<EOF
|
||||
haxe_url=$(curl https://api.github.com/repos/HaxeFoundation/haxe/releases -sfL \
|
||||
| jq '.[] | select(.name == "'"$haxe_version"'")' \
|
||||
| jq '.assets[] | select(.name | endswith("linux64.tar.gz"))' \
|
||||
| jq -r '.browser_download_url')
|
||||
curl -sfL "$haxe_url" | tar -xz -C /usr/local
|
||||
#haxe_url=$(curl https://api.github.com/repos/HaxeFoundation/haxe/releases -fL \
|
||||
# | jq '.[] | select(.name == "'"$haxe_version"'")' \
|
||||
# | jq '.assets[] | select(.name | endswith("linux64.tar.gz"))' \
|
||||
# | jq -r '.browser_download_url')
|
||||
haxe_url="https://geo.thei.rs/funkin/haxe-4.3.6-linux64.tar.gz"
|
||||
curl -fL "$haxe_url" | tar -xz -C /usr/local
|
||||
EOF
|
||||
|
||||
RUN <<EOF
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
- NOTE: By performing this operation, you are downloading Content which is proprietary and protected by national and international copyright and trademark laws. See [the LICENSE.md file for the Funkin.assets](https://github.com/FunkinCrew/funkin.assets/blob/main/LICENSE.md) repo for more information.
|
||||
5. Run `haxelib --global install hmm` and then `haxelib --global run hmm setup` to install hmm.json
|
||||
6. Run `hmm install` to install all haxelibs of the current branch
|
||||
7. Run `haxelib run lime setup` to set up lime
|
||||
7. Run `haxelib run lime setup` to set up Lime
|
||||
8. Perform additional platform setup
|
||||
- For Windows, download the [Visual Studio Build Tools](https://aka.ms/vs/17/release/vs_BuildTools.exe)
|
||||
- When prompted, select "Individual Components" and make sure to download the following:
|
||||
|
@ -21,6 +21,8 @@
|
|||
- Windows 10/11 SDK
|
||||
- Mac: [`lime setup mac` Documentation](https://lime.openfl.org/docs/advanced-setup/macos/)
|
||||
- Linux: [`lime setup linux` Documentation](https://lime.openfl.org/docs/advanced-setup/linux/)
|
||||
- Note: Funkin's fork currently doesn't come with the necessary binaries so you'll have to rebuild Lime. See [Troubleshooting](TROUBLESHOOTING.md#lime-related-issues).
|
||||
- One of Funkin's dependencies uses libVLC, which requires you to install some packages to be able to compile: `sudo apt install libvlc-dev libvlccore-dev libvlccore9`
|
||||
- HTML5: Compiles without any extra setup
|
||||
9. If you are targeting for native, you may need to run `lime rebuild <PLATFORM>` and `lime rebuild <PLATFORM> -debug`
|
||||
10. `lime test <PLATFORM>` to build and launch the game for your platform (for example, `lime test windows`)
|
||||
|
|
10
docs/COMPILING_MAC.md
Normal file
10
docs/COMPILING_MAC.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
# Mac Compiling Guide + Considerations
|
||||
|
||||
There's a few extra considerations when compiling FNF for Mac that *we* have to handle when creating a wider release.
|
||||
- [Creating a Universal Binary](#creating-a-universal-binary)
|
||||
- Code-signing
|
||||
- Notarizing
|
||||
|
||||
## Creating a Universal Binary
|
||||
|
||||
Run the `art/macos-universal.sh` script, which automatically compiles release versions of both arm64 and x86 of Funkin. You can also see there for reference of how it's done.
|
|
@ -1,6 +1,50 @@
|
|||
# Contributing
|
||||
Welcome to the Contributing Guide!
|
||||
You can contribute to the Funkin' repository by opening issues or pull requests. This guide will cover best practices for each type of contribution.
|
||||
You can contribute to the Funkin' repository by opening issues or pull requests.
|
||||
|
||||
This guide will cover best practices for each type of contribution.
|
||||
|
||||
# Table of Contents
|
||||
<details open>
|
||||
<summary><b>Contents</b></summary>
|
||||
|
||||
[Part 1: Etiquette](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#part-1-etiquette)
|
||||
|
||||
<details>
|
||||
<summary><a href="https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#part-2-issues">Part 2: Issues</a></summary>
|
||||
|
||||
* [Requirements](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#requirements)
|
||||
|
||||
* [Rejected Features](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#rejected-features)
|
||||
|
||||
* [Issue Types](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#issue-types)
|
||||
|
||||
* [Before You Submit...](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#before-you-submit)
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
|
||||
<summary><a href="https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#part-3-pull-requests">Part 3: Pull Requests</a></summary>
|
||||
|
||||
* [Choosing a base branch](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#choosing-a-base-branch)
|
||||
|
||||
* [Merge conflicts and rebasing](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#merge-conflicts-and-rebasing)
|
||||
|
||||
* [Code PRs](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#code-prs)
|
||||
|
||||
* [Documentation PRs](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#documentation-prs)
|
||||
|
||||
* [GitHub PRs](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#github-prs)
|
||||
|
||||
* [funkin.assets PRs](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#funkinassets-prs)
|
||||
|
||||
</details>
|
||||
|
||||
[Closing](https://github.com/FunkinCrew/Funkin/blob/main/docs/CONTRIBUTING.md#closing)
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
# Part 1: Etiquette
|
||||
- Be respectful to one another. We're here to help each other out!
|
||||
|
@ -27,13 +71,21 @@ Here's a list of commonly suggested features and the reasons why they won't be a
|
|||
| Combo Break + Accuracy Displays | https://github.com/FunkinCrew/Funkin/pull/2681#issuecomment-2156308982 |
|
||||
| Toggleable Ghost Tapping | https://github.com/FunkinCrew/Funkin/pull/2564#issuecomment-2119701802 |
|
||||
| Perfectly Centered Strumlines | _same as above^_ |
|
||||
| MultiKey, 9k, More than 4 keys, etc. | https://github.com/FunkinCrew/Funkin/issues/4243#issuecomment-2692371969 |
|
||||
| Losing Icons for DD and Parents | https://github.com/FunkinCrew/Funkin/issues/3048#issuecomment-2243491536 |
|
||||
| Playable GF / Speaker BF / Speaker Pico | https://github.com/FunkinCrew/Funkin/issues/2953#issuecomment-2216985230 |
|
||||
| Fresh (Chill Mix) as Title Screen Music | https://github.com/FunkinCrew/Funkin/pull/4282#issuecomment-2709334718 |
|
||||
| Adjusted Difficulty Ratings | https://github.com/FunkinCrew/Funkin/issues/2781#issuecomment-2172053144 |
|
||||
| Countdown after Unpausing | https://github.com/FunkinCrew/Funkin/issues/2721#issuecomment-2159330106 |
|
||||
| Difficulty Ratings above 20 | https://github.com/FunkinCrew/Funkin/issues/3075#issuecomment-2368984497 |
|
||||
| Ability to Reset a Song's Score | https://github.com/FunkinCrew/Funkin/issues/3916#issuecomment-2525408261 |
|
||||
| Quick Restart Keybind (not R) | https://github.com/FunkinCrew/Funkin/issues/3268#issuecomment-2351095232 |
|
||||
| Countdown after Unpausing Song | https://github.com/FunkinCrew/Funkin/issues/2721#issuecomment-2159330106 |
|
||||
| 4:3 Aspect Ratio for Week 6 | https://github.com/FunkinCrew/Funkin/issues/3840#issuecomment-2689158438 |
|
||||
| "Philly Glow" Effect from Psych Engine | https://github.com/FunkinCrew/Funkin/issues/3788#issuecomment-2688966982 |
|
||||
| Importing Charts from Psych Engine (and other mod content) | https://github.com/FunkinCrew/Funkin/issues/2586#issuecomment-2125733327 |
|
||||
| Backwards Compatibility for Modding | https://github.com/FunkinCrew/Funkin/issues/3949#issuecomment-2608391329 |
|
||||
| Lua Support | https://github.com/FunkinCrew/Funkin/issues/2643#issuecomment-2143718093 |
|
||||
| Lua Support | https://github.com/FunkinCrew/Funkin/issues/2643#issuecomment-2143718093 |
|
||||
|
||||
|
||||
## Issue Types
|
||||
Choose the issue template that best suits your needs!
|
||||
|
@ -192,7 +244,9 @@ Here are some guidelines for writing comments in your code:
|
|||
|
||||
## Documentation PRs
|
||||
Documentation-based PRs make changes such as **fixing typos** or **adding new information** in documentation files.
|
||||
|
||||
This involves modifying one or several of the repository’s `.md` files, found throughout the repository.
|
||||
|
||||
Make sure your changes are easy to understand and formatted consistently to maximize clarity and readability.
|
||||
|
||||
> [!CAUTION]
|
||||
|
@ -229,16 +283,21 @@ Make sure your changes are easy to understand and formatted consistently to maxi
|
|||
|
||||
## GitHub PRs
|
||||
GitHub-related PRs make changes such as **tweaking Issue Templates** or **updating the repository’s workflows**.
|
||||
|
||||
This involves modifying one or several of the repository’s `.yml` files, or any other file in the `.github` folder.
|
||||
|
||||
Please test these changes on your fork’s main branch to avoid breaking anything in this repository (e.g. GitHub Actions, issue templates, etc.)!
|
||||
|
||||
## funkin.assets PRs
|
||||
The `assets` submodule has its own repository called [funkin.assets](https://github.com/FunkinCrew/funkin.assets).
|
||||
|
||||
If you only modify files in the `assets` folder, open a PR in the `funkin.assets` repository instead of the main repository.
|
||||
|
||||
If you simultaneously modify files from both repositories, then open two separate PRs and explain the connection in your PR descriptions.
|
||||
|
||||
Be sure to choose `main` as the base branch for `funkin.assets` PRs, as no `develop` branch exists for that repository.
|
||||
|
||||
# Closing
|
||||
Thank you for reading the Contributing Guide.
|
||||
|
||||
We look forward to seeing your contributions to the game!
|
||||
|
|
|
@ -7,6 +7,8 @@ Most of this functionality is only available on debug builds of the game!
|
|||
- `F3`: ***SCREENSHOT***: Takes a screenshot of the game and saves it to the local `screenshots` directory. Works outside of debug builds too!
|
||||
- `F4`: ***EJECT***: Forcibly switch state to the Main Menu (with no extra transition). Useful if you're stuck in a level and you need to get out!
|
||||
- `F5`: ***HOT RELOAD***: Forcibly reload the game's scripts and data files, then restart the current state. If any files in the `assets` folder have been modified, the game should process the changes for you! NOTE: Known bug, this does not reset song charts or song scripts, but it should reset everything else (such as stage layout data and character animation data).
|
||||
- `CTRL-ALT-SHIFT-L`: ***FORCE CRASH***: Immediately crash the game with a detailed crash log and a stack trace. (Only works in the Main Menu on debug builds).
|
||||
|
||||
- `CTRL-SHIFT-L`: ***FORCE CRASH***: Immediately crash the game with a detailed crash log and a stack trace.
|
||||
|
||||
## **Play State**
|
||||
|
@ -29,3 +31,8 @@ Most of this functionality is only available on debug builds of the game!
|
|||
## **Main Menu**
|
||||
- `~`: ***DEBUG***: Opens a menu to access the Chart Editor and other work-in-progress editors. Rebindable in the options menu.
|
||||
- `CTRL-ALT-SHIFT-W`: ***ALL ACCESS***: Unlocks all songs in Freeplay. Only available on debug builds.
|
||||
- `CTRL-ALT-SHIFT-M`: ***NO MORE ACCESS***: Re-locks all songs in Freeplay except those unlocked by default. Only available on debug builds.
|
||||
- `CTRL-ALT-SHIFT-R`: ***GREAT SCORE?***: Give the user a hypothetical overridden score, and see if we can maintain that golden P rank. Only available on debug builds.
|
||||
- `CTRL-ALT-SHIFT-P`: ***CHARACTER UNLOCK SCREEN***: Forces the Character Select screen to play Pico's unlocking animation. Only available on debug builds.
|
||||
- `CTRL-ALT-SHIFT-N`: ***CHARACTER NOT SEEN***: Marks all characters as not seen and enables BF's new character unlocked animation in Freeplay. Only available on debug builds.
|
||||
- `CTRL-ALT-SHIFT-E`: ***DUMP SAVE DATA***: Prompts the user to save their save data as a JSON file, so its contents can be viewed. Only available on debug builds.
|
|
@ -29,3 +29,22 @@
|
|||
- You did not clone the repository correctly! Copy the path to your `funkin` folder and run `cd the\path\you\copied`. Then follow the compilation guide starting from **Step 4**.
|
||||
|
||||
- Other compilation issues may be caused by installing bad library versions. Try deleting the `.haxelib` folder and following the guide starting from **Step 5**.
|
||||
|
||||
## Lime Related Issues
|
||||
- Segmentation fault and/or crash after `Done mapping time changes: [SongTimeChange(0ms,102bpm)]`
|
||||
- Caused by using official Lime instead of Funkin's fork. Reinstalling Lime should fix it.
|
||||
(NOTE: Make sure you do this via `hmm` (e.g `hmm reinstall -f lime`) to guarantee you get Funkin's version of Lime.)
|
||||
|
||||
- `Uncaught exception - Could not find lime.ndll.` ... `Advanced users may run "lime rebuild cpp" instead.`
|
||||
- Usually specific to Linux. Running the commands below should fix it.
|
||||
```
|
||||
cd .haxelib/lime/git
|
||||
git submodule init
|
||||
git submodule sync
|
||||
git submodule update
|
||||
cd ../../..
|
||||
# Note: The command and packages here might be different depending on your distro.
|
||||
sudo apt install libgl1-mesa-dev libglu1-mesa-dev g++ g++-multilib gcc-multilib libasound2-dev libx11-dev libxext-dev libxi-dev libxrandr-dev libxinerama-dev libpulse-dev
|
||||
lime rebuild cpp -64 -release -clean
|
||||
```
|
||||
For Windows or MacOS you can download pre-built binaries from [Funkin's Lime](https://github.com/FunkinCrew/lime/tree/dev-funkin/ndll).
|
||||
|
|
41
hmm.json
41
hmm.json
|
@ -4,21 +4,21 @@
|
|||
"name": "FlxPartialSound",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "a1eab7b9bf507b87200a3341719054fe427f3b15",
|
||||
"ref": "41f35ddb1eb9d10bc742e6f8b5bcc62f9ef8ad84",
|
||||
"url": "https://github.com/FunkinCrew/FlxPartialSound.git"
|
||||
},
|
||||
{
|
||||
"name": "flixel",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "ffa691cb2d2d81de35b900a4411e4062ac84ab58",
|
||||
"ref": "fffb1a74cf08f63dacc2ab09976340563f5b6e6d",
|
||||
"url": "https://github.com/FunkinCrew/flixel"
|
||||
},
|
||||
{
|
||||
"name": "flixel-addons",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "7424db4e9164ff46f224a7c47de1b732d2542dd7",
|
||||
"ref": "b9118f47f43a66bc0e5fbfcfd9903f0425e918ee",
|
||||
"url": "https://github.com/FunkinCrew/flixel-addons"
|
||||
},
|
||||
{
|
||||
|
@ -32,7 +32,7 @@
|
|||
"name": "flxanimate",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "0654797e5eb7cd7de0c1b2dbaa1efe5a1e1d9412",
|
||||
"ref": "713d3de0e566d6cd54cde13711ab3e4f60f59f4b",
|
||||
"url": "https://github.com/Dot-Stuff/flxanimate"
|
||||
},
|
||||
{
|
||||
|
@ -44,7 +44,7 @@
|
|||
"name": "funkin.vis",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "22b1ce089dd924f15cdc4632397ef3504d464e90",
|
||||
"ref": "1966f8fbbbc509ed90d4b520f3c49c084fc92fd6",
|
||||
"url": "https://github.com/FunkinCrew/funkVis"
|
||||
},
|
||||
{
|
||||
|
@ -63,7 +63,7 @@
|
|||
"name": "haxeui-core",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "51c23588614397089a5ce182cddea729f0be6fa0",
|
||||
"ref": "74ba53387eab0c4c2d3825e103fe70df8e46d9b1",
|
||||
"url": "https://github.com/haxeui/haxeui-core"
|
||||
},
|
||||
{
|
||||
|
@ -77,22 +77,15 @@
|
|||
"name": "hscript",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "12785398e2f07082f05034cb580682e5671442a2",
|
||||
"ref": "27c86f9a761c1d16d4433c4cf252eccb7b2e18de",
|
||||
"url": "https://github.com/FunkinCrew/hscript"
|
||||
},
|
||||
{
|
||||
"name": "hxCodec",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "61b98a7a353b7f529a8fec84ed9afc919a2dffdd",
|
||||
"url": "https://github.com/FunkinCrew/hxCodec"
|
||||
},
|
||||
{
|
||||
"name": "hxcpp",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "c6bac3d6c7d683f25104296b2f4c50f8c90b8349",
|
||||
"url": "https://github.com/cortex-engine/hxcpp"
|
||||
"ref": "v4.3.75",
|
||||
"url": "https://github.com/HaxeFoundation/hxcpp"
|
||||
},
|
||||
{
|
||||
"name": "hxcpp-debug-server",
|
||||
|
@ -120,6 +113,11 @@
|
|||
"type": "haxelib",
|
||||
"version": "1.3.0"
|
||||
},
|
||||
{
|
||||
"name": "hxvlc",
|
||||
"type": "haxelib",
|
||||
"version": "2.0.1"
|
||||
},
|
||||
{
|
||||
"name": "json2object",
|
||||
"type": "git",
|
||||
|
@ -145,7 +143,7 @@
|
|||
"name": "lime",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "fe3368f611a84a19afc03011353945ae4da8fffd",
|
||||
"ref": "d1322e60f97b5c6e977f9e3e8a04f22b5190e7d9",
|
||||
"url": "https://github.com/FunkinCrew/lime"
|
||||
},
|
||||
{
|
||||
|
@ -176,11 +174,18 @@
|
|||
"ref": "f61be7f7ba796595f45023ca65164a485aba0e7e",
|
||||
"url": "https://github.com/FunkinCrew/MassiveUnit"
|
||||
},
|
||||
{
|
||||
"name": "newgrounds",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "c8b30832027fa04f48fe6a45fa7903d5b265d56e",
|
||||
"url": "https://github.com/Geokureli/Newgrounds/"
|
||||
},
|
||||
{
|
||||
"name": "openfl",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "8306425c497766739510ab29e876059c96f77bd2",
|
||||
"ref": "d061c936b462f040304ec2bd42d9f59d2e59e285",
|
||||
"url": "https://github.com/FunkinCrew/openfl"
|
||||
},
|
||||
{
|
||||
|
|
73
project.hxp
73
project.hxp
|
@ -25,7 +25,7 @@ class Project extends HXProject {
|
|||
* REMEMBER TO CHANGE THIS WHEN THE GAME UPDATES!
|
||||
* You only have to change it here, the rest of the game will query this value.
|
||||
*/
|
||||
static final VERSION:String = "0.5.3";
|
||||
static final VERSION:String = "0.6.2";
|
||||
|
||||
/**
|
||||
* The game's name. Used as the default window title.
|
||||
|
@ -178,6 +178,33 @@ class Project extends HXProject {
|
|||
*/
|
||||
static final FEATURE_NEWGROUNDS:FeatureFlag = "FEATURE_NEWGROUNDS";
|
||||
|
||||
/**
|
||||
* `-DFEATURE_NEWGROUNDS_DEBUG`
|
||||
* If this flag is enabled, the game will enable Newgrounds.io's debug functions.
|
||||
* This provides additional information in requests, as well as "faking" medal and leaderboard submissions.
|
||||
*/
|
||||
static final FEATURE_NEWGROUNDS_DEBUG:FeatureFlag = "FEATURE_NEWGROUNDS_DEBUG";
|
||||
|
||||
/**
|
||||
* `-DFEATURE_NEWGROUNDS_AUTOLOGIN`
|
||||
* If this flag is enabled, the game will attempt to automatically login to Newgrounds on startup.
|
||||
*/
|
||||
static final FEATURE_NEWGROUNDS_AUTOLOGIN:FeatureFlag = "FEATURE_NEWGROUNDS_AUTOLOGIN";
|
||||
|
||||
/**
|
||||
* `-DFEATURE_NEWGROUNDS_TESTING_MEDALS`
|
||||
* If this flag is enabled, use the medal IDs from the debug test bench.
|
||||
* If disabled, use the actual medal IDs from the release project on Newgrounds.
|
||||
*/
|
||||
static final FEATURE_NEWGROUNDS_TESTING_MEDALS:FeatureFlag = "FEATURE_NEWGROUNDS_TESTING_MEDALS";
|
||||
|
||||
/**
|
||||
* `-DFEATURE_NEWGROUNDS_EVENTS`
|
||||
* If this flag is enabled, the game will attempt to send events to Newgrounds when the user does stuff.
|
||||
* This lets us see cool anonymized stats! It only works if the user is logged in.
|
||||
*/
|
||||
static final FEATURE_NEWGROUNDS_EVENTS:FeatureFlag = "FEATURE_NEWGROUNDS_EVENTS";
|
||||
|
||||
/**
|
||||
* `-DFEATURE_FUNKVIS`
|
||||
* If this flag is enabled, the game will enable the Funkin Visualizer library.
|
||||
|
@ -197,7 +224,7 @@ class Project extends HXProject {
|
|||
/**
|
||||
* `-DFEATURE_VIDEO_PLAYBACK`
|
||||
* If this flag is enabled, the game will enable support for video playback.
|
||||
* This requires the hxCodec library on desktop platforms.
|
||||
* This requires the hxvlc library on desktop platforms.
|
||||
*/
|
||||
static final FEATURE_VIDEO_PLAYBACK:FeatureFlag = "FEATURE_VIDEO_PLAYBACK";
|
||||
|
||||
|
@ -232,6 +259,12 @@ class Project extends HXProject {
|
|||
*/
|
||||
static final FEATURE_STAGE_EDITOR:FeatureFlag = "FEATURE_STAGE_EDITOR";
|
||||
|
||||
/**
|
||||
* `-DFEATURE_RESULTS_DEBUG
|
||||
* If this flag is enabled, a debug menu for Results screen will be accessible from the debug menu.
|
||||
*/
|
||||
static final FEATURE_RESULTS_DEBUG:FeatureFlag = "FEATURE_RESULTS_DEBUG";
|
||||
|
||||
/**
|
||||
* `-DFEATURE_POLYMOD_MODS`
|
||||
* If this flag is enabled, the game will enable the Polymod library's support for atomic mod loading from the `./mods` folder.
|
||||
|
@ -330,6 +363,8 @@ class Project extends HXProject {
|
|||
|
||||
this.window.hardware = true;
|
||||
this.window.vsync = false;
|
||||
// force / allow high DPI
|
||||
this.window.allowHighDPI = true;
|
||||
|
||||
if (isWeb()) {
|
||||
this.window.resizable = true;
|
||||
|
@ -460,7 +495,6 @@ class Project extends HXProject {
|
|||
|
||||
// Should be false unless explicitly requested.
|
||||
GITHUB_BUILD.apply(this, false);
|
||||
FEATURE_NEWGROUNDS.apply(this, false);
|
||||
FEATURE_GHOST_TAPPING.apply(this, false);
|
||||
|
||||
// Should be true unless explicitly requested.
|
||||
|
@ -474,11 +508,18 @@ class Project extends HXProject {
|
|||
|
||||
// Should be true on debug builds or if GITHUB_BUILD is enabled.
|
||||
FEATURE_DEBUG_FUNCTIONS.apply(this, isDebug() || GITHUB_BUILD.isEnabled(this));
|
||||
FEATURE_RESULTS_DEBUG.apply(this, isDebug() || GITHUB_BUILD.isEnabled(this));
|
||||
|
||||
// Got a lot of complains about this being turned off by default on some builds.
|
||||
// TODO: Look into ways to optimize logging (maybe by using a thread pool?)
|
||||
FEATURE_LOG_TRACE.apply(this, true);
|
||||
|
||||
FEATURE_NEWGROUNDS.apply(this, true);
|
||||
FEATURE_NEWGROUNDS_DEBUG.apply(this, false);
|
||||
FEATURE_NEWGROUNDS_TESTING_MEDALS.apply(this, FEATURE_NEWGROUNDS.isEnabled(this) && FEATURE_DEBUG_FUNCTIONS.isEnabled(this));
|
||||
FEATURE_NEWGROUNDS_AUTOLOGIN.apply(this, FEATURE_NEWGROUNDS.isEnabled(this) && isWeb());
|
||||
FEATURE_NEWGROUNDS_EVENTS.apply(this, FEATURE_NEWGROUNDS.isEnabled(this));
|
||||
|
||||
// Should default to true on workspace builds and false on release builds.
|
||||
REDIRECT_ASSETS_FOLDER.apply(this, isDebug() && isDesktop());
|
||||
|
||||
|
@ -579,7 +620,7 @@ class Project extends HXProject {
|
|||
|
||||
// Ensure all Flixel classes are available at runtime.
|
||||
// Explicitly ignore packages which require additional dependencies.
|
||||
addHaxeMacro("include('flixel', true, [ 'flixel.addons.editors.spine.*', 'flixel.addons.nape.*', 'flixel.system.macros.*' ])");
|
||||
addHaxeMacro("include('flixel', true, [ 'flixel.addons.editors.spine.*', 'flixel.addons.nape.*', 'flixel.system.macros.*', 'flixel.addons.tile.FlxRayCastTilemap' ])");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -593,7 +634,15 @@ class Project extends HXProject {
|
|||
function configureOutputDir() {
|
||||
// Set the output directory. Depends on the target platform and build type.
|
||||
|
||||
var buildDir = 'export/${isDebug() ? 'debug' : 'release'}/';
|
||||
var buildDir = 'export/${isDebug() ? 'debug' : 'release'}';
|
||||
|
||||
// we use a dedicated 'tracy' folder, since it generally needs a recompile when in use
|
||||
if (FEATURE_DEBUG_TRACY.isEnabled(this))
|
||||
buildDir += "-tracy";
|
||||
|
||||
// trailing slash might not be needed, works fine on macOS without it, but I haven't tested on Windows!
|
||||
buildDir += "/";
|
||||
|
||||
|
||||
info('Output directory: $buildDir');
|
||||
// setenv('BUILD_DIR', buildDir);
|
||||
|
@ -650,9 +699,9 @@ class Project extends HXProject {
|
|||
}
|
||||
|
||||
if (isDesktop() && !isHashLink() && FEATURE_VIDEO_PLAYBACK.isEnabled(this)) {
|
||||
// hxCodec doesn't function on HashLink or non-desktop platforms
|
||||
// hxvlc doesn't function on HashLink or non-desktop platforms
|
||||
// It's also unnecessary if video playback is disabled
|
||||
addHaxelib('hxCodec'); // Video playback
|
||||
addHaxelib('hxvlc'); // Video playback
|
||||
}
|
||||
|
||||
if (FEATURE_DISCORD_RPC.isEnabled(this)) {
|
||||
|
@ -1041,15 +1090,7 @@ class Project extends HXProject {
|
|||
* Display an info message. This should not interfere with the build process.
|
||||
*/
|
||||
public function info(message:String):Void {
|
||||
// CURSED: We have to disable info() log calls because of a bug.
|
||||
// https://github.com/haxelime/lime-vscode-extension/issues/88
|
||||
|
||||
// Log.info('[INFO] ${message}');
|
||||
|
||||
// trace(message);
|
||||
// Sys.println(message);
|
||||
// Sys.stdout().writeString(message);
|
||||
// Sys.stderr().writeString(message);
|
||||
if(command != "display") { Log.info('[INFO] ${message}'); }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,10 +9,23 @@ class Prebuild
|
|||
{
|
||||
static inline final BUILD_TIME_FILE:String = '.build_time';
|
||||
|
||||
static final NG_CREDS_PATH:String = './source/funkin/api/newgrounds/NewgroundsCredentials.hx';
|
||||
|
||||
static final NG_CREDS_TEMPLATE:String = "package funkin.api.newgrounds;
|
||||
|
||||
class NewgroundsCredentials
|
||||
{
|
||||
public static final APP_ID:String = #if API_NG_APP_ID haxe.macro.Compiler.getDefine(\"API_NG_APP_ID\") #else 'INSERT APP ID HERE' #end;
|
||||
public static final ENCRYPTION_KEY:String = #if API_NG_ENC_KEY haxe.macro.Compiler.getDefine(\"API_NG_ENC_KEY\") #else 'INSERT ENCRYPTION KEY HERE' #end;
|
||||
}";
|
||||
|
||||
static function main():Void
|
||||
{
|
||||
saveBuildTime();
|
||||
trace('Building...');
|
||||
|
||||
saveBuildTime();
|
||||
|
||||
buildCredsFile();
|
||||
}
|
||||
|
||||
static function saveBuildTime():Void
|
||||
|
@ -22,4 +35,22 @@ class Prebuild
|
|||
fo.writeDouble(now);
|
||||
fo.close();
|
||||
}
|
||||
|
||||
static function buildCredsFile():Void
|
||||
{
|
||||
#if sys
|
||||
if (sys.FileSystem.exists(NG_CREDS_PATH))
|
||||
{
|
||||
trace('NewgroundsCredentials.hx already exists, skipping.');
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('Creating NewgroundsCredentials.hx...');
|
||||
|
||||
var fileContents:String = NG_CREDS_TEMPLATE;
|
||||
|
||||
sys.io.File.saveContent(NG_CREDS_PATH, fileContents);
|
||||
}
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,9 @@ import openfl.display.BitmapData;
|
|||
#if FEATURE_DISCORD_RPC
|
||||
import funkin.api.discord.DiscordClient;
|
||||
#end
|
||||
#if FEATURE_NEWGROUNDS
|
||||
import funkin.api.newgrounds.NewgroundsClient;
|
||||
#end
|
||||
|
||||
/**
|
||||
* A core class which performs initialization of the game.
|
||||
|
@ -82,6 +85,10 @@ class InitState extends FlxState
|
|||
// Disable the thing on Windows where it tries to send a bug report to Microsoft because why do they care?
|
||||
WindowUtil.disableCrashHandler();
|
||||
|
||||
#if FEATURE_DEBUG_TRACY
|
||||
funkin.util.WindowUtil.initTracy();
|
||||
#end
|
||||
|
||||
// This ain't a pixel art game! (most of the time)
|
||||
FlxSprite.defaultAntialiasing = true;
|
||||
|
||||
|
@ -117,8 +124,8 @@ class InitState extends FlxState
|
|||
//
|
||||
// NEWGROUNDS API SETUP
|
||||
//
|
||||
#if newgrounds
|
||||
NGio.init();
|
||||
#if FEATURE_NEWGROUNDS
|
||||
NewgroundsClient.instance.init();
|
||||
#end
|
||||
|
||||
//
|
||||
|
@ -151,6 +158,7 @@ class InitState extends FlxState
|
|||
#if FEATURE_SCREENSHOTS
|
||||
funkin.util.plugins.ScreenshotPlugin.initialize();
|
||||
#end
|
||||
funkin.util.plugins.NewgroundsMedalPlugin.initialize();
|
||||
funkin.util.plugins.EvacuateDebugPlugin.initialize();
|
||||
funkin.util.plugins.ForceCrashPlugin.initialize();
|
||||
funkin.util.plugins.ReloadAssetsDebugPlugin.initialize();
|
||||
|
@ -232,7 +240,7 @@ class InitState extends FlxState
|
|||
storyMode: true,
|
||||
title: "Cum Song Erect by Kawai Sprite",
|
||||
songId: "cum",
|
||||
characterId: "pico-playable",
|
||||
characterId: "pico",
|
||||
difficultyId: "nightmare",
|
||||
isNewHighscore: true,
|
||||
scoreData:
|
||||
|
@ -248,9 +256,10 @@ class InitState extends FlxState
|
|||
combo: 69,
|
||||
maxCombo: 69,
|
||||
totalNotesHit: 140,
|
||||
totalNotes: 190
|
||||
totalNotes: 240
|
||||
}
|
||||
// 2400 total notes = 7% = LOSS
|
||||
// 275 total notes = 69% = NICE
|
||||
// 240 total notes = 79% = GOOD
|
||||
// 230 total notes = 82% = GREAT
|
||||
// 210 total notes = 91% = EXCELLENT
|
||||
|
|
|
@ -2,11 +2,9 @@ package funkin;
|
|||
|
||||
import funkin.save.Save;
|
||||
import funkin.input.Controls;
|
||||
import flixel.FlxCamera;
|
||||
import funkin.input.PreciseInputManager;
|
||||
import flixel.input.actions.FlxActionInput;
|
||||
import flixel.input.gamepad.FlxGamepad;
|
||||
import flixel.util.FlxSignal;
|
||||
import flixel.util.FlxSignal.FlxTypedSignal;
|
||||
|
||||
/**
|
||||
* A core class which represents the current player(s) and their controls and other configuration.
|
||||
|
|
|
@ -218,6 +218,121 @@ class Preferences
|
|||
public static var lockedFramerateFunction = untyped js.Syntax.code("window.requestAnimationFrame");
|
||||
#end
|
||||
|
||||
/**
|
||||
* If >0, the game will display a semi-opaque background under the notes.
|
||||
* `0` for no background, `100` for solid black if you're freaky like that
|
||||
* @default `0`
|
||||
*/
|
||||
public static var strumlineBackgroundOpacity(get, set):Int;
|
||||
|
||||
static function get_strumlineBackgroundOpacity():Int
|
||||
{
|
||||
return (Save?.instance?.options?.strumlineBackgroundOpacity ?? 0);
|
||||
}
|
||||
|
||||
static function set_strumlineBackgroundOpacity(value:Int):Int
|
||||
{
|
||||
var save:Save = Save.instance;
|
||||
save.options.strumlineBackgroundOpacity = value;
|
||||
save.flush();
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* If enabled, the game will hide the mouse when taking a screenshot.
|
||||
* @default `true`
|
||||
*/
|
||||
public static var shouldHideMouse(get, set):Bool;
|
||||
|
||||
static function get_shouldHideMouse():Bool
|
||||
{
|
||||
return Save?.instance?.options?.screenshot?.shouldHideMouse ?? true;
|
||||
}
|
||||
|
||||
static function set_shouldHideMouse(value:Bool):Bool
|
||||
{
|
||||
var save:Save = Save.instance;
|
||||
save.options.screenshot.shouldHideMouse = value;
|
||||
save.flush();
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* If enabled, the game will show a preview after taking a screenshot.
|
||||
* @default `true`
|
||||
*/
|
||||
public static var fancyPreview(get, set):Bool;
|
||||
|
||||
static function get_fancyPreview():Bool
|
||||
{
|
||||
return Save?.instance?.options?.screenshot?.fancyPreview ?? true;
|
||||
}
|
||||
|
||||
static function set_fancyPreview(value:Bool):Bool
|
||||
{
|
||||
var save:Save = Save.instance;
|
||||
save.options.screenshot.fancyPreview = value;
|
||||
save.flush();
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* If enabled, the game will show the preview only after a screenshot is saved.
|
||||
* @default `true`
|
||||
*/
|
||||
public static var previewOnSave(get, set):Bool;
|
||||
|
||||
static function get_previewOnSave():Bool
|
||||
{
|
||||
return Save?.instance?.options?.screenshot?.previewOnSave ?? true;
|
||||
}
|
||||
|
||||
static function set_previewOnSave(value:Bool):Bool
|
||||
{
|
||||
var save:Save = Save.instance;
|
||||
save.options.screenshot.previewOnSave = value;
|
||||
save.flush();
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* The game will save any screenshots taken to this format.
|
||||
* @default `PNG`
|
||||
*/
|
||||
public static var saveFormat(get, set):Any;
|
||||
|
||||
static function get_saveFormat():Any
|
||||
{
|
||||
return Save?.instance?.options?.screenshot?.saveFormat ?? 'PNG';
|
||||
}
|
||||
|
||||
static function set_saveFormat(value):Any
|
||||
{
|
||||
var save:Save = Save.instance;
|
||||
save.options.screenshot.saveFormat = value;
|
||||
save.flush();
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* The game will save JPEG screenshots with this quality percentage.
|
||||
* @default `80`
|
||||
*/
|
||||
public static var jpegQuality(get, set):Int;
|
||||
|
||||
static function get_jpegQuality():Int
|
||||
{
|
||||
return Save?.instance?.options?.screenshot?.jpegQuality ?? 80;
|
||||
}
|
||||
|
||||
static function set_jpegQuality(value:Int):Int
|
||||
{
|
||||
var save:Save = Save.instance;
|
||||
save.options.screenshot.jpegQuality = value;
|
||||
save.flush();
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the user's preferences from the save data and apply them.
|
||||
*/
|
||||
|
|
|
@ -2,7 +2,10 @@ package funkin.api.discord;
|
|||
|
||||
#if FEATURE_DISCORD_RPC
|
||||
import hxdiscord_rpc.Discord;
|
||||
import hxdiscord_rpc.Types;
|
||||
import hxdiscord_rpc.Types.DiscordButton;
|
||||
import hxdiscord_rpc.Types.DiscordEventHandlers;
|
||||
import hxdiscord_rpc.Types.DiscordRichPresence;
|
||||
import hxdiscord_rpc.Types.DiscordUser;
|
||||
import sys.thread.Thread;
|
||||
|
||||
class DiscordClient
|
||||
|
|
106
source/funkin/api/newgrounds/Events.hx
Normal file
106
source/funkin/api/newgrounds/Events.hx
Normal file
|
@ -0,0 +1,106 @@
|
|||
package funkin.api.newgrounds;
|
||||
|
||||
import io.newgrounds.Call.CallOutcome;
|
||||
import io.newgrounds.NG;
|
||||
import io.newgrounds.objects.events.Outcome;
|
||||
import io.newgrounds.objects.events.Result;
|
||||
|
||||
/**
|
||||
* Use Newgrounds to perform basic telemetry. Ignore if not logged in to Newgrounds.
|
||||
*/
|
||||
@:nullSafety
|
||||
class Events
|
||||
{
|
||||
// Only allow letters, numbers, spaces, dashes, and underscores.
|
||||
static final EVENT_NAME_REGEX:EReg = ~/[^a-zA-Z0-9 -_]/g;
|
||||
|
||||
public static function logEvent(eventName:String):Void
|
||||
{
|
||||
#if (FEATURE_NEWGROUNDS && FEATURE_NEWGROUNDS_EVENTS)
|
||||
if (NewgroundsClient.instance.isLoggedIn())
|
||||
{
|
||||
var eventHandler = NG.core.calls.event;
|
||||
|
||||
if (eventHandler != null)
|
||||
{
|
||||
var sanitizedEventName = EVENT_NAME_REGEX.replace(eventName, '');
|
||||
var outcomeHandler = onEventLogged.bind(sanitizedEventName, _);
|
||||
eventHandler.logEvent(sanitizedEventName).addOutcomeHandler(outcomeHandler).send();
|
||||
}
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
static function onEventLogged(eventName:String, outcome:CallOutcome<LogEventData>)
|
||||
{
|
||||
switch (outcome)
|
||||
{
|
||||
case SUCCESS(data):
|
||||
trace('[NEWGROUNDS] Logged event: ${data.eventName}');
|
||||
case FAIL(outcome):
|
||||
switch (outcome)
|
||||
{
|
||||
case HTTP(error):
|
||||
trace('[NEWGROUNDS] HTTP error while logging event: ${error}');
|
||||
case RESPONSE(error):
|
||||
trace('[NEWGROUNDS] Response error (${error.code}) while logging event: ${error.message}');
|
||||
case RESULT(error):
|
||||
switch (error.code)
|
||||
{
|
||||
case 103: // Invalid custom event name
|
||||
trace('[NEWGROUNDS] Invalid custom event name: ${eventName}');
|
||||
default:
|
||||
trace('[NEWGROUNDS] Result error (${error.code}) while logging event: ${error.message}');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static inline function logStartGame():Void
|
||||
{
|
||||
logEvent('start-game');
|
||||
}
|
||||
|
||||
public static inline function logStartSong(songId:String, variation:String):Void
|
||||
{
|
||||
logEvent('start-song_${songId}-${variation}');
|
||||
}
|
||||
|
||||
public static inline function logFailSong(songId:String, variation:String):Void
|
||||
{
|
||||
logEvent('blueballs_${songId}-${variation}');
|
||||
}
|
||||
|
||||
public static inline function logCompleteSong(songId:String, variation:String):Void
|
||||
{
|
||||
logEvent('complete-song_${songId}-${variation}');
|
||||
}
|
||||
|
||||
public static inline function logStartLevel(levelId:String):Void
|
||||
{
|
||||
logEvent('start-level_${levelId}');
|
||||
}
|
||||
|
||||
public static inline function logCompleteLevel(levelId:String):Void
|
||||
{
|
||||
logEvent('complete-level_${levelId}');
|
||||
}
|
||||
|
||||
public static inline function logEarnRank(rankName:String):Void
|
||||
{
|
||||
logEvent('earn-rank_${rankName}');
|
||||
}
|
||||
|
||||
public static inline function logWatchCartoon():Void
|
||||
{
|
||||
logEvent('watch-cartoon');
|
||||
}
|
||||
|
||||
// Note there is already a loadReferral call for the merch link
|
||||
// and that gets logged as an event!
|
||||
|
||||
public static inline function logOpenCredits():Void
|
||||
{
|
||||
logEvent('open-credits');
|
||||
}
|
||||
}
|
395
source/funkin/api/newgrounds/Leaderboards.hx
Normal file
395
source/funkin/api/newgrounds/Leaderboards.hx
Normal file
|
@ -0,0 +1,395 @@
|
|||
package funkin.api.newgrounds;
|
||||
|
||||
#if FEATURE_NEWGROUNDS
|
||||
import io.newgrounds.Call.CallError;
|
||||
import io.newgrounds.objects.ScoreBoard as LeaderboardData;
|
||||
import io.newgrounds.objects.events.Outcome;
|
||||
import io.newgrounds.utils.ScoreBoardList;
|
||||
|
||||
class Leaderboards
|
||||
{
|
||||
public static function listLeaderboardData():Map<Leaderboard, LeaderboardData>
|
||||
{
|
||||
var leaderboardList:Null<ScoreBoardList> = NewgroundsClient.instance.leaderboards;
|
||||
if (leaderboardList == null)
|
||||
{
|
||||
trace('[NEWGROUNDS] Not logged in, cannot fetch medal data!');
|
||||
return [];
|
||||
}
|
||||
else
|
||||
{
|
||||
var result:Map<Leaderboard, LeaderboardData> = [];
|
||||
|
||||
for (leaderboardId in leaderboardList.keys())
|
||||
{
|
||||
var leaderboardData = leaderboardList.get(leaderboardId);
|
||||
if (leaderboardData == null) continue;
|
||||
|
||||
// A little hacky, but it works.
|
||||
result.set(cast leaderboardId, leaderboardData);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit a score to Newgrounds.
|
||||
* @param leaderboard The leaderboard to submit to.
|
||||
* @param score The score to submit.
|
||||
* @param tag An optional tag to attach to the score.
|
||||
*/
|
||||
public static function submitScore(leaderboard:Leaderboard, score:Int, ?tag:String):Void
|
||||
{
|
||||
// Silently reject submissions for unknown leaderboards.
|
||||
if (leaderboard == Leaderboard.Unknown) return;
|
||||
|
||||
if (NewgroundsClient.instance.isLoggedIn())
|
||||
{
|
||||
var leaderboardList = NewgroundsClient.instance.leaderboards;
|
||||
if (leaderboardList == null) return;
|
||||
|
||||
var leaderboardData:Null<LeaderboardData> = leaderboardList.get(leaderboard.getId());
|
||||
if (leaderboardData != null)
|
||||
{
|
||||
leaderboardData.postScore(score, function(outcome:Outcome<CallError>):Void {
|
||||
switch (outcome)
|
||||
{
|
||||
case SUCCESS:
|
||||
trace('[NEWGROUNDS] Submitted score!');
|
||||
case FAIL(error):
|
||||
trace('[NEWGROUNDS] Failed to submit score!');
|
||||
trace(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit a score for a Story Level to Newgrounds.
|
||||
*/
|
||||
public static function submitLevelScore(levelId:String, difficultyId:String, score:Int):Void
|
||||
{
|
||||
var tag = '${difficultyId}';
|
||||
Leaderboards.submitScore(Leaderboard.getLeaderboardByLevel(levelId), score, tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit a score for a song to Newgrounds.
|
||||
*/
|
||||
public static function submitSongScore(songId:String, difficultyId:String, score:Int):Void
|
||||
{
|
||||
var tag = '${difficultyId}';
|
||||
Leaderboards.submitScore(Leaderboard.getLeaderboardBySong(songId, difficultyId), score, tag);
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
enum abstract Leaderboard(Int)
|
||||
{
|
||||
/**
|
||||
* Represents an undefined or invalid leaderboard.
|
||||
*/
|
||||
var Unknown = -1;
|
||||
|
||||
//
|
||||
// STORY LEVELS
|
||||
//
|
||||
var StoryWeek1 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14239 #else 9615 #end;
|
||||
var StoryWeek2 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14240 #else 9616 #end;
|
||||
var StoryWeek3 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14242 #else 9767 #end;
|
||||
var StoryWeek4 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14241 #else 9866 #end;
|
||||
var StoryWeek5 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14243 #else 9956 #end;
|
||||
var StoryWeek6 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14244 #else 9957 #end;
|
||||
var StoryWeek7 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14245 #else 14682 #end;
|
||||
var StoryWeekend1 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14237 #else 14683 #end;
|
||||
|
||||
//
|
||||
// SONGS
|
||||
//
|
||||
// Tutorial
|
||||
var Tutorial = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14249 #else 14684 #end;
|
||||
|
||||
// Week 1
|
||||
var Bopeebo = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14246 #else 9603 #end;
|
||||
var BopeeboErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14685 #end;
|
||||
var BopeeboPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14686 #end;
|
||||
var Fresh = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14247 #else 9602 #end;
|
||||
var FreshErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14687 #end;
|
||||
var FreshPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14688 #end;
|
||||
var DadBattle = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 14248 #else 9605 #end;
|
||||
var DadBattleErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14689 #end;
|
||||
var DadBattlePicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14690 #end;
|
||||
|
||||
// Week 2
|
||||
var Spookeez = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9604 #end;
|
||||
var SpookeezErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14691 #end;
|
||||
var SpookeezPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14692 #end;
|
||||
var South = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9606 #end;
|
||||
var SouthErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14693 #end;
|
||||
var SouthPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14694 #end;
|
||||
var Monster = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14703 #end;
|
||||
|
||||
// Week 3
|
||||
var Pico = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9766 #end;
|
||||
var PicoErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14695 #end;
|
||||
var PicoPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14696 #end;
|
||||
var PhillyNice = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9769 #end;
|
||||
var PhillyNiceErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14697 #end;
|
||||
var PhillyNicePicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14698 #end;
|
||||
var Blammed = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9768 #end;
|
||||
var BlammedErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14704 #end;
|
||||
var BlammedPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14705 #end;
|
||||
|
||||
// Week 4
|
||||
var SatinPanties = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9869 #end;
|
||||
var SatinPantiesErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14701 #end;
|
||||
var High = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9867 #end;
|
||||
var HighErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14699 #end;
|
||||
var MILF = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9868 #end;
|
||||
|
||||
// Week 5
|
||||
var Cocoa = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14706 #end;
|
||||
var CocoaErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14707 #end;
|
||||
var CocoaPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14708 #end;
|
||||
var Eggnog = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14709 #end;
|
||||
var EggnogErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14711 #end;
|
||||
var EggnogPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14710 #end;
|
||||
var WinterHorrorland = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14712 #end;
|
||||
|
||||
// Week 6
|
||||
var Senpai = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9958 #end;
|
||||
var SenpaiErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14713 #end;
|
||||
var SenpaiPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14716 #end;
|
||||
var Roses = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9959 #end;
|
||||
var RosesErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14714 #end;
|
||||
var RosesPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14717 #end;
|
||||
var Thorns = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 9960 #end;
|
||||
var ThornsErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14715 #end;
|
||||
|
||||
// Week 7
|
||||
var Ugh = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14718 #end;
|
||||
var UghErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14722 #end;
|
||||
var UghPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14721 #end;
|
||||
var Guns = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14719 #end;
|
||||
var GunsPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14723 #end;
|
||||
var Stress = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14720 #end;
|
||||
var StressPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14724 #end;
|
||||
|
||||
// Weekend 1
|
||||
var Darnell = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14725 #end;
|
||||
var DarnellErect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14727 #end;
|
||||
var DarnellBFMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14726 #end;
|
||||
var LitUp = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14728 #end;
|
||||
var LitUpBFMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14729 #end;
|
||||
var TwoHot = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14730 #end; // Variable names can't start with a number!
|
||||
var Blazin = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 1000000 #else 14731 #end;
|
||||
|
||||
public function getId():Int
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the leaderboard for a given level and difficulty.
|
||||
* @param levelId The ID for the story level.
|
||||
* @param difficulty The current difficulty.
|
||||
* @return The Leaderboard ID for the given level and difficulty.
|
||||
*/
|
||||
public static function getLeaderboardByLevel(levelId:String):Leaderboard
|
||||
{
|
||||
switch (levelId)
|
||||
{
|
||||
case "week1":
|
||||
return StoryWeek1;
|
||||
case "week2":
|
||||
return StoryWeek2;
|
||||
case "week3":
|
||||
return StoryWeek3;
|
||||
case "week4":
|
||||
return StoryWeek4;
|
||||
case "week5":
|
||||
return StoryWeek5;
|
||||
case "week6":
|
||||
return StoryWeek6;
|
||||
case "week7":
|
||||
return StoryWeek7;
|
||||
case "weekend1":
|
||||
return StoryWeekend1;
|
||||
default:
|
||||
return Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the leaderboard for a given level and difficulty.
|
||||
* @param levelId The ID for the story level.
|
||||
* @param difficulty The current difficulty, suffixed with the variation, like `easy-pico` or `nightmare`.
|
||||
* @return The Leaderboard ID for the given level and difficulty.
|
||||
*/
|
||||
public static function getLeaderboardBySong(songId:String, difficulty:String):Leaderboard
|
||||
{
|
||||
var variation = Constants.DEFAULT_VARIATION;
|
||||
var difficultyParts = difficulty.split('-');
|
||||
|
||||
if (difficultyParts.length >= 2)
|
||||
{
|
||||
variation = difficultyParts[difficultyParts.length - 1];
|
||||
}
|
||||
else if (Constants.DEFAULT_DIFFICULTY_LIST_ERECT.contains(difficulty))
|
||||
{
|
||||
variation = "erect";
|
||||
}
|
||||
|
||||
switch (variation)
|
||||
{
|
||||
case "pico":
|
||||
switch (songId)
|
||||
{
|
||||
case "bopeebo":
|
||||
return BopeeboPicoMix;
|
||||
case "fresh":
|
||||
return FreshPicoMix;
|
||||
case "dadbattle":
|
||||
return DadBattlePicoMix;
|
||||
case "spookeez":
|
||||
return SpookeezPicoMix;
|
||||
case "south":
|
||||
return SouthPicoMix;
|
||||
case "pico":
|
||||
return PicoPicoMix;
|
||||
case "philly-nice":
|
||||
return PhillyNicePicoMix;
|
||||
case "blammed":
|
||||
return BlammedPicoMix;
|
||||
case "cocoa":
|
||||
return CocoaPicoMix;
|
||||
case "eggnog":
|
||||
return EggnogPicoMix;
|
||||
case "senpai":
|
||||
return SenpaiPicoMix;
|
||||
case "roses":
|
||||
return RosesPicoMix;
|
||||
case "ugh":
|
||||
return UghPicoMix;
|
||||
case "guns":
|
||||
return GunsPicoMix;
|
||||
case "stress":
|
||||
return StressPicoMix;
|
||||
default:
|
||||
return Unknown;
|
||||
}
|
||||
case "bf":
|
||||
switch (songId)
|
||||
{
|
||||
case "darnell":
|
||||
return DarnellBFMix;
|
||||
case "litup":
|
||||
return LitUpBFMix;
|
||||
default:
|
||||
return Unknown;
|
||||
}
|
||||
case "erect":
|
||||
switch (songId)
|
||||
{
|
||||
case "bopeebo":
|
||||
return BopeeboErect;
|
||||
case "fresh":
|
||||
return FreshErect;
|
||||
case "dadbattle":
|
||||
return DadBattleErect;
|
||||
case "spookeez":
|
||||
return SpookeezErect;
|
||||
case "south":
|
||||
return SouthErect;
|
||||
case "pico":
|
||||
return PicoErect;
|
||||
case "philly-nice":
|
||||
return PhillyNiceErect;
|
||||
case "blammed":
|
||||
return BlammedErect;
|
||||
case "satin-panties":
|
||||
return SatinPantiesErect;
|
||||
case "high":
|
||||
return HighErect;
|
||||
case "cocoa":
|
||||
return CocoaErect;
|
||||
case "eggnog":
|
||||
return EggnogErect;
|
||||
case "senpai":
|
||||
return SenpaiErect;
|
||||
case "roses":
|
||||
return RosesErect;
|
||||
case "thorns":
|
||||
return ThornsErect;
|
||||
case "ugh":
|
||||
return UghErect;
|
||||
case "darnell":
|
||||
return DarnellErect;
|
||||
default:
|
||||
return Unknown;
|
||||
}
|
||||
case "default":
|
||||
switch (songId)
|
||||
{
|
||||
case "tutorial":
|
||||
return Tutorial;
|
||||
case "bopeebo":
|
||||
return Bopeebo;
|
||||
case "fresh":
|
||||
return Fresh;
|
||||
case "dadbattle":
|
||||
return DadBattle;
|
||||
case "spookeez":
|
||||
return Spookeez;
|
||||
case "south":
|
||||
return South;
|
||||
case "monster":
|
||||
return Monster;
|
||||
case "pico":
|
||||
return Pico;
|
||||
case "philly-nice":
|
||||
return PhillyNice;
|
||||
case "blammed":
|
||||
return Blammed;
|
||||
case "satin-panties":
|
||||
return SatinPanties;
|
||||
case "high":
|
||||
return High;
|
||||
case "milf":
|
||||
return MILF;
|
||||
case "cocoa":
|
||||
return Cocoa;
|
||||
case "eggnog":
|
||||
return Eggnog;
|
||||
case "winter-horrorland":
|
||||
return WinterHorrorland;
|
||||
case "senpai":
|
||||
return Senpai;
|
||||
case "roses":
|
||||
return Roses;
|
||||
case "thorns":
|
||||
return Thorns;
|
||||
case "ugh":
|
||||
return Ugh;
|
||||
case "guns":
|
||||
return Guns;
|
||||
case "stress":
|
||||
return Stress;
|
||||
case "darnell":
|
||||
return Darnell;
|
||||
case "litup":
|
||||
return LitUp;
|
||||
case "2hot":
|
||||
return TwoHot;
|
||||
case "blazin":
|
||||
return Blazin;
|
||||
default:
|
||||
return Unknown;
|
||||
}
|
||||
default:
|
||||
return Unknown;
|
||||
}
|
||||
}
|
||||
}
|
346
source/funkin/api/newgrounds/Medals.hx
Normal file
346
source/funkin/api/newgrounds/Medals.hx
Normal file
|
@ -0,0 +1,346 @@
|
|||
package funkin.api.newgrounds;
|
||||
|
||||
#if FEATURE_NEWGROUNDS
|
||||
import io.newgrounds.objects.Medal as MedalData;
|
||||
import funkin.util.plugins.NewgroundsMedalPlugin;
|
||||
import flixel.graphics.FlxGraphic;
|
||||
import openfl.display.BitmapData;
|
||||
import io.newgrounds.utils.MedalList;
|
||||
import haxe.Json;
|
||||
|
||||
class Medals
|
||||
{
|
||||
public static var medalJSON:Array<MedalJSON> = [];
|
||||
|
||||
public static function listMedalData():Map<Medal, MedalData>
|
||||
{
|
||||
var medalList = NewgroundsClient.instance.medals;
|
||||
|
||||
if (medalList == null)
|
||||
{
|
||||
trace('[NEWGROUNDS] Not logged in, cannot fetch medal data!');
|
||||
return [];
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Why do I have to do this, @:nullSafety is fucked up
|
||||
var result:Map<Medal, MedalData> = [];
|
||||
|
||||
for (medalId in medalList.keys())
|
||||
{
|
||||
var medalData = medalList.get(medalId);
|
||||
if (medalData == null) continue;
|
||||
|
||||
// A little hacky, but it works.
|
||||
result.set(cast medalId, medalData);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public static function award(medal:Medal):Void
|
||||
{
|
||||
if (NewgroundsClient.instance.isLoggedIn())
|
||||
{
|
||||
var medalList = NewgroundsClient.instance.medals;
|
||||
if (medalList == null) return;
|
||||
|
||||
var medalData:Null<MedalData> = medalList.get(medal.getId());
|
||||
@:privateAccess
|
||||
if (medalData == null || medalData._data == null)
|
||||
{
|
||||
trace('[NEWGROUNDS] Could not retrieve data for medal: ${medal}');
|
||||
return;
|
||||
}
|
||||
else if (!medalData.unlocked)
|
||||
{
|
||||
trace('[NEWGROUNDS] Awarding medal (${medal}).');
|
||||
medalData.sendUnlock();
|
||||
|
||||
// Play the medal unlock animation, but only if the user has not already unlocked it.
|
||||
#if html5
|
||||
// Web builds support parsing the bitmap data from the URL directly.
|
||||
BitmapData.loadFromFile("https:" + medalData.icon).onComplete(function(bmp:BitmapData) {
|
||||
var medalGraphic = FlxGraphic.fromBitmapData(bmp);
|
||||
medalGraphic.persist = true;
|
||||
NewgroundsMedalPlugin.play(medalData.value, medalData.name, medalGraphic);
|
||||
});
|
||||
#else
|
||||
if (medalJSON == null) loadMedalJSON();
|
||||
// We have to use a medal image from the game files. We use a Base64 encoded image that NG spits out.
|
||||
// TODO: Wait, don't they give us the medal icon?
|
||||
|
||||
var localMedalData:Null<MedalJSON> = medalJSON.filter(function(jsonMedal) {
|
||||
#if FEATURE_NEWGROUNDS_TESTING_MEDALS
|
||||
return medal == jsonMedal.idTest;
|
||||
#else
|
||||
return medal == jsonMedal.id;
|
||||
#end
|
||||
})[0];
|
||||
|
||||
if (localMedalData == null) throw "You forgot to encode a Base64 image for medal: " + medal;
|
||||
|
||||
var str:String = localMedalData.icon;
|
||||
// Lime/OpenFL parses it without the included prefix stuff, so we remove it.
|
||||
str = str.replace("data:image/png;base64,", "").trim();
|
||||
var bitmapData = BitmapData.fromBase64(str, "image/png");
|
||||
var medalGraphic:Null<FlxGraphic> = null;
|
||||
if (str != null)
|
||||
{
|
||||
medalGraphic = FlxGraphic.fromBitmapData(bitmapData);
|
||||
medalGraphic.persist = true;
|
||||
}
|
||||
|
||||
NewgroundsMedalPlugin.play(medalData.value, medalData.name, medalGraphic);
|
||||
#end
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('[NEWGROUNDS] User already has medal (${medal}).');
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('[NEWGROUNDS] Attempted to award medal (${medal}), but not logged into Newgrounds.');
|
||||
}
|
||||
}
|
||||
|
||||
public static function loadMedalJSON():Void
|
||||
{
|
||||
var jsonPath = Paths.json('medals');
|
||||
|
||||
var jsonString = Assets.getText(jsonPath);
|
||||
|
||||
var parser = new json2object.JsonParser<Array<MedalJSON>>();
|
||||
parser.ignoreUnknownVariables = false;
|
||||
trace('[NEWGROUNDS] Parsing local medal data...');
|
||||
parser.fromJson(jsonString, jsonPath);
|
||||
|
||||
if (parser.errors.length > 0)
|
||||
{
|
||||
trace('[NEWGROUNDS] Failed to parse local medal data!');
|
||||
for (error in parser.errors)
|
||||
funkin.data.DataError.printError(error);
|
||||
medalJSON = [];
|
||||
}
|
||||
else
|
||||
{
|
||||
medalJSON = parser.value;
|
||||
}
|
||||
}
|
||||
|
||||
public static function awardStoryLevel(id:String):Void
|
||||
{
|
||||
switch (id)
|
||||
{
|
||||
case 'tutorial':
|
||||
Medals.award(Medal.StoryTutorial);
|
||||
case 'week1':
|
||||
Medals.award(Medal.StoryWeek1);
|
||||
case 'week2':
|
||||
Medals.award(Medal.StoryWeek2);
|
||||
case 'week3':
|
||||
Medals.award(Medal.StoryWeek3);
|
||||
case 'week4':
|
||||
Medals.award(Medal.StoryWeek4);
|
||||
case 'week5':
|
||||
Medals.award(Medal.StoryWeek5);
|
||||
case 'week6':
|
||||
Medals.award(Medal.StoryWeek6);
|
||||
case 'week7':
|
||||
Medals.award(Medal.StoryWeek7);
|
||||
case 'weekend1':
|
||||
Medals.award(Medal.StoryWeekend1);
|
||||
default:
|
||||
trace('[NEWGROUNDS] Story level does not have a medal! (${id}).');
|
||||
}
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
/**
|
||||
* Represents NG Medal data in a JSON format.
|
||||
*/
|
||||
typedef MedalJSON =
|
||||
{
|
||||
/**
|
||||
* Medal ID to use for release builds
|
||||
*/
|
||||
var id:Int;
|
||||
|
||||
/**
|
||||
* Medal ID to use for testing builds
|
||||
*/
|
||||
var idTest:Int;
|
||||
|
||||
/**
|
||||
* The English name for the medal
|
||||
*/
|
||||
var name:String;
|
||||
|
||||
/**
|
||||
* The English name for the medal
|
||||
*/
|
||||
var icon:String;
|
||||
}
|
||||
|
||||
enum abstract Medal(Int) from Int to Int
|
||||
{
|
||||
/**
|
||||
* Represents an undefined or invalid medal.
|
||||
*/
|
||||
var Unknown = -1;
|
||||
|
||||
/**
|
||||
* I Said Funkin'!
|
||||
* Start the game for the first time.
|
||||
*/
|
||||
var StartGame = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80894 #else 60960 #end;
|
||||
|
||||
/**
|
||||
* That's How You Do It!
|
||||
* Beat Tutoria l in Story Mode (on any difficulty).
|
||||
*/
|
||||
var StoryTutorial = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80906 #else 83647 #end;
|
||||
|
||||
/**
|
||||
* More Like Daddy Queerest
|
||||
* Beat Week 1 in Story Mode (on any difficulty).
|
||||
*/
|
||||
var StoryWeek1 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80899 #else 60961 #end;
|
||||
|
||||
/**
|
||||
* IT IS THE SPOOKY MONTH
|
||||
* Beat Week 2 in Story Mode (on any difficulty).
|
||||
*/
|
||||
var StoryWeek2 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80900 #else 83648 #end;
|
||||
|
||||
/**
|
||||
* Pico Funny
|
||||
* Beat Week 3 in Story Mode (on any difficulty).
|
||||
*/
|
||||
var StoryWeek3 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80901 #else 83649 #end;
|
||||
|
||||
/**
|
||||
* Mommy Must Murder
|
||||
* Beat Week 4 in Story Mode (on any difficulty).
|
||||
*/
|
||||
var StoryWeek4 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80902 #else 83650 #end;
|
||||
|
||||
/**
|
||||
* Yule Tide Joy
|
||||
* Beat Week 5 in Story Mode (on any difficulty).
|
||||
*/
|
||||
var StoryWeek5 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80903 #else 83651 #end;
|
||||
|
||||
/**
|
||||
* A Visual Novelty
|
||||
* Beat Week 6 in Story Mode (on any difficulty).
|
||||
*/
|
||||
var StoryWeek6 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80904 #else 83652 #end;
|
||||
|
||||
/**
|
||||
* I <3 JohnnyUtah
|
||||
* Beat Week 7 in Story Mode (on any difficulty).
|
||||
*/
|
||||
var StoryWeek7 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80905 #else 83653 #end;
|
||||
|
||||
/**
|
||||
* Yo, Really Think So?
|
||||
* Beat Weekend 1 in Story Mode (on any difficulty).
|
||||
*/
|
||||
var StoryWeekend1 = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80907 #else 83654 #end;
|
||||
|
||||
/**
|
||||
* Stay Funky
|
||||
* Press TAB in Freeplay and unlock your first character.
|
||||
*/
|
||||
var CharSelect = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 83633 #else 83655 #end;
|
||||
|
||||
/**
|
||||
* A Challenger Appears
|
||||
* Beat any Pico remix in Freeplay (on any difficulty).
|
||||
*/
|
||||
var FreeplayPicoMix = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80910 #else 83656 #end;
|
||||
|
||||
/**
|
||||
* De-Stressing
|
||||
* Beat Stress (Pico Mix) in Freeplay (on Normal difficulty or higher).
|
||||
*/
|
||||
var FreeplayStressPico = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 83629 #else 83657 #end;
|
||||
|
||||
/**
|
||||
* L
|
||||
* Earn a Loss rating on any song (on any difficulty).
|
||||
*/
|
||||
var LossRating = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80915 #else 83658 #end;
|
||||
|
||||
/**
|
||||
* Getting Freaky
|
||||
* Earn a Perfect rating on any song on Hard difficulty or higher.
|
||||
* NOTE: Should also be awarded for a Gold Perfect because otherwise that would be annoying.
|
||||
*/
|
||||
var PerfectRatingHard = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80908 #else 83659 #end;
|
||||
|
||||
/**
|
||||
* You Should Drink More Water
|
||||
* Earn a Golden Perfect rating on any song on Hard difficulty or higher.
|
||||
*/
|
||||
var GoldPerfectRatingHard = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80909 #else 83660 #end;
|
||||
|
||||
/**
|
||||
* Harder Than Hard
|
||||
* Beat any Erect remix in Freeplay on Erect or Nightmare difficulty.
|
||||
*/
|
||||
var ErectDifficulty = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80911 #else 83661 #end;
|
||||
|
||||
/**
|
||||
* The Rap God
|
||||
* Earn a Gold Perfect rating on any song on Nightmare difficulty.
|
||||
*/
|
||||
var GoldPerfectRatingNightmare = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80912 #else 83662 #end;
|
||||
|
||||
/**
|
||||
* Just like the game!
|
||||
* Get freaky on a Friday.
|
||||
* NOTE: You must beat at least one song on any difficulty.
|
||||
*/
|
||||
var FridayNight = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80913 #else 61034 #end;
|
||||
|
||||
/**
|
||||
* Nice
|
||||
* Earn a rating of EXACTLY 69% (good luck).
|
||||
*/
|
||||
var Nice = #if FEATURE_NEWGROUNDS_TESTING_MEDALS 80914 #else 83646 #end;
|
||||
|
||||
public function getId():Int
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
public static function getMedalByStoryLevel(levelId:String):Medal
|
||||
{
|
||||
switch (levelId)
|
||||
{
|
||||
case "week1":
|
||||
return StoryWeek1;
|
||||
case "week2":
|
||||
return StoryWeek2;
|
||||
case "week3":
|
||||
return StoryWeek3;
|
||||
case "week4":
|
||||
return StoryWeek4;
|
||||
case "week5":
|
||||
return StoryWeek5;
|
||||
case "week6":
|
||||
return StoryWeek6;
|
||||
case "week7":
|
||||
return StoryWeek7;
|
||||
case "weekend1":
|
||||
return StoryWeekend1;
|
||||
default:
|
||||
return Unknown;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
package funkin.api.newgrounds;
|
||||
|
||||
#if newgrounds
|
||||
import io.newgrounds.NG;
|
||||
import io.newgrounds.NGLite;
|
||||
import io.newgrounds.components.ScoreBoardComponent.Period;
|
||||
import io.newgrounds.objects.Error;
|
||||
import io.newgrounds.objects.Medal;
|
||||
import io.newgrounds.objects.Score;
|
||||
import io.newgrounds.objects.ScoreBoard;
|
||||
import io.newgrounds.objects.events.Response;
|
||||
import io.newgrounds.objects.events.Result.GetCurrentVersionResult;
|
||||
import io.newgrounds.objects.events.Result.GetVersionResult;
|
||||
#end
|
||||
|
||||
/**
|
||||
* Contains any script functions which should be BLOCKED from use by modded scripts.
|
||||
*/
|
||||
class NGUnsafe
|
||||
{
|
||||
static public function logEvent(event:String)
|
||||
{
|
||||
#if newgrounds
|
||||
NG.core.calls.event.logEvent(event).send();
|
||||
trace('should have logged: ' + event);
|
||||
#else
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
trace('event:$event - not logged, missing NG.io lib');
|
||||
#end
|
||||
#end
|
||||
}
|
||||
|
||||
static public function unlockMedal(id:Int)
|
||||
{
|
||||
#if newgrounds
|
||||
if (isLoggedIn)
|
||||
{
|
||||
var medal = NG.core.medals.get(id);
|
||||
if (!medal.unlocked) medal.sendUnlock();
|
||||
}
|
||||
#else
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
trace('medal:$id - not unlocked, missing NG.io lib');
|
||||
#end
|
||||
#end
|
||||
}
|
||||
|
||||
static public function postScore(score:Int = 0, song:String)
|
||||
{
|
||||
#if newgrounds
|
||||
if (isLoggedIn)
|
||||
{
|
||||
for (id in NG.core.scoreBoards.keys())
|
||||
{
|
||||
var board = NG.core.scoreBoards.get(id);
|
||||
|
||||
if (song == board.name)
|
||||
{
|
||||
board.postScore(score, "Uhh meow?");
|
||||
}
|
||||
|
||||
// trace('loaded scoreboard id:$id, name:${board.name}');
|
||||
}
|
||||
}
|
||||
#else
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
trace('Song:$song, Score:$score - not posted, missing NG.io lib');
|
||||
#end
|
||||
#end
|
||||
}
|
||||
}
|
|
@ -1,243 +0,0 @@
|
|||
package funkin.api.newgrounds;
|
||||
|
||||
import flixel.util.FlxSignal;
|
||||
import flixel.util.FlxTimer;
|
||||
import lime.app.Application;
|
||||
import openfl.display.Stage;
|
||||
#if newgrounds
|
||||
import io.newgrounds.NG;
|
||||
import io.newgrounds.NGLite;
|
||||
import io.newgrounds.components.ScoreBoardComponent.Period;
|
||||
import io.newgrounds.objects.Error;
|
||||
import io.newgrounds.objects.Medal;
|
||||
import io.newgrounds.objects.Score;
|
||||
import io.newgrounds.objects.ScoreBoard;
|
||||
import io.newgrounds.objects.events.Response;
|
||||
import io.newgrounds.objects.events.Result.GetCurrentVersionResult;
|
||||
import io.newgrounds.objects.events.Result.GetVersionResult;
|
||||
#end
|
||||
|
||||
/**
|
||||
* Contains any script functions which should be ALLOWD for use by modded scripts.
|
||||
*/
|
||||
class NGUtil
|
||||
{
|
||||
#if newgrounds
|
||||
/**
|
||||
* True, if the saved sessionId was used in the initial login, and failed to connect.
|
||||
* Used in MainMenuState to show a popup to establish a new connection
|
||||
*/
|
||||
public static var savedSessionFailed(default, null):Bool = false;
|
||||
|
||||
public static var scoreboardsLoaded:Bool = false;
|
||||
public static var isLoggedIn(get, never):Bool;
|
||||
|
||||
inline static function get_isLoggedIn()
|
||||
{
|
||||
return NG.core != null && NG.core.loggedIn;
|
||||
}
|
||||
|
||||
public static var scoreboardArray:Array<Score> = [];
|
||||
|
||||
public static var ngDataLoaded(default, null):FlxSignal = new FlxSignal();
|
||||
public static var ngScoresLoaded(default, null):FlxSignal = new FlxSignal();
|
||||
|
||||
public static var GAME_VER:String = "";
|
||||
|
||||
static public function checkVersion(callback:String->Void)
|
||||
{
|
||||
trace('checking NG.io version');
|
||||
GAME_VER = "v" + Application.current.meta.get('version');
|
||||
|
||||
NG.core.calls.app.getCurrentVersion(GAME_VER).addDataHandler(function(response) {
|
||||
GAME_VER = response.result.data.currentVersion;
|
||||
trace('CURRENT NG VERSION: ' + GAME_VER);
|
||||
callback(GAME_VER);
|
||||
}).send();
|
||||
}
|
||||
|
||||
static public function init()
|
||||
{
|
||||
var api = APIStuff.API;
|
||||
if (api == null || api.length == 0)
|
||||
{
|
||||
trace("Missing Newgrounds API key, aborting connection");
|
||||
return;
|
||||
}
|
||||
trace("connecting to newgrounds");
|
||||
|
||||
#if NG_FORCE_EXPIRED_SESSION
|
||||
var sessionId:String = "fake_session_id";
|
||||
function onSessionFail(error:Error)
|
||||
{
|
||||
trace("Forcing an expired saved session. " + "To disable, comment out NG_FORCE_EXPIRED_SESSION in Project.xml");
|
||||
savedSessionFailed = true;
|
||||
}
|
||||
#else
|
||||
var sessionId:String = NGLite.getSessionId();
|
||||
if (sessionId != null) trace("found web session id");
|
||||
|
||||
#if (debug)
|
||||
if (sessionId == null && APIStuff.SESSION != null)
|
||||
{
|
||||
trace("using debug session id");
|
||||
sessionId = APIStuff.SESSION;
|
||||
}
|
||||
#end
|
||||
|
||||
var onSessionFail:Error->Void = null;
|
||||
if (sessionId == null && Save.instance.ngSessionId != null)
|
||||
{
|
||||
trace("using stored session id");
|
||||
sessionId = Save.instance.ngSessionId;
|
||||
onSessionFail = function(error) savedSessionFailed = true;
|
||||
}
|
||||
#end
|
||||
|
||||
NG.create(api, sessionId, #if NG_DEBUG true #else false #end, onSessionFail);
|
||||
|
||||
#if NG_VERBOSE
|
||||
NG.core.verbose = true;
|
||||
#end
|
||||
// Set the encryption cipher/format to RC4/Base64. AES128 and Hex are not implemented yet
|
||||
NG.core.initEncryption(APIStuff.EncKey); // Found in you NG project view
|
||||
|
||||
if (NG.core.attemptingLogin)
|
||||
{
|
||||
/* a session_id was found in the loadervars, this means the user is playing on newgrounds.com
|
||||
* and we should login shortly. lets wait for that to happen
|
||||
*/
|
||||
trace("attempting login");
|
||||
NG.core.onLogin.add(onNGLogin);
|
||||
}
|
||||
// GK: taking out auto login, adding a login button to the main menu
|
||||
// else
|
||||
// {
|
||||
// /* They are NOT playing on newgrounds.com, no session id was found. We must start one manually, if we want to.
|
||||
// * Note: This will cause a new browser window to pop up where they can log in to newgrounds
|
||||
// */
|
||||
// NG.core.requestLogin(onNGLogin);
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to log in to newgrounds by requesting a new session ID, only call if no session ID was found automatically
|
||||
* @param popupLauncher The function to call to open the login url, must be inside
|
||||
* a user input event or the popup blocker will block it.
|
||||
* @param onComplete A callback with the result of the connection.
|
||||
*/
|
||||
static public function login(?popupLauncher:(Void->Void)->Void, onComplete:ConnectionResult->Void)
|
||||
{
|
||||
trace("Logging in manually");
|
||||
var onPending:Void->Void = null;
|
||||
if (popupLauncher != null)
|
||||
{
|
||||
onPending = function() popupLauncher(NG.core.openPassportUrl);
|
||||
}
|
||||
|
||||
var onSuccess:Void->Void = onNGLogin;
|
||||
var onFail:Error->Void = null;
|
||||
var onCancel:Void->Void = null;
|
||||
if (onComplete != null)
|
||||
{
|
||||
onSuccess = function() {
|
||||
onNGLogin();
|
||||
onComplete(Success);
|
||||
}
|
||||
onFail = function(e) onComplete(Fail(e.message));
|
||||
onCancel = function() onComplete(Cancelled);
|
||||
}
|
||||
|
||||
NG.core.requestLogin(onSuccess, onPending, onFail, onCancel);
|
||||
}
|
||||
|
||||
inline static public function cancelLogin():Void
|
||||
{
|
||||
NG.core.cancelLoginRequest();
|
||||
}
|
||||
|
||||
static function onNGLogin():Void
|
||||
{
|
||||
trace('logged in! user:${NG.core.user.name}');
|
||||
Save.instance.ngSessionId = NG.core.sessionId;
|
||||
Save.instance.flush();
|
||||
// Load medals then call onNGMedalFetch()
|
||||
NG.core.requestMedals(onNGMedalFetch);
|
||||
|
||||
// Load Scoreboards hten call onNGBoardsFetch()
|
||||
NG.core.requestScoreBoards(onNGBoardsFetch);
|
||||
|
||||
ngDataLoaded.dispatch();
|
||||
}
|
||||
|
||||
static public function logout()
|
||||
{
|
||||
NG.core.logOut();
|
||||
|
||||
Save.instance.ngSessionId = null;
|
||||
Save.instance.flush();
|
||||
}
|
||||
|
||||
// --- MEDALS
|
||||
static function onNGMedalFetch():Void
|
||||
{
|
||||
/*
|
||||
// Reading medal info
|
||||
for (id in NG.core.medals.keys())
|
||||
{
|
||||
var medal = NG.core.medals.get(id);
|
||||
trace('loaded medal id:$id, name:${medal.name}, description:${medal.description}');
|
||||
}
|
||||
|
||||
// Unlocking medals
|
||||
var unlockingMedal = NG.core.medals.get(54352);// medal ids are listed in your NG project viewer
|
||||
if (!unlockingMedal.unlocked)
|
||||
unlockingMedal.sendUnlock();
|
||||
*/
|
||||
}
|
||||
|
||||
// --- SCOREBOARDS
|
||||
static function onNGBoardsFetch():Void
|
||||
{
|
||||
/*
|
||||
// Reading medal info
|
||||
for (id in NG.core.scoreBoards.keys())
|
||||
{
|
||||
var board = NG.core.scoreBoards.get(id);
|
||||
trace('loaded scoreboard id:$id, name:${board.name}');
|
||||
}
|
||||
*/
|
||||
// var board = NG.core.scoreBoards.get(8004);// ID found in NG project view
|
||||
|
||||
// Posting a score thats OVER 9000!
|
||||
// board.postScore(FlxG.random.int(0, 1000));
|
||||
|
||||
// --- To view the scores you first need to select the range of scores you want to see ---
|
||||
|
||||
// add an update listener so we know when we get the new scores
|
||||
// board.onUpdate.add(onNGScoresFetch);
|
||||
trace("shoulda got score by NOW!");
|
||||
// board.requestScores(20);// get the best 10 scores ever logged
|
||||
// more info on scores --- http://www.newgrounds.io/help/components/#scoreboard-getscores
|
||||
}
|
||||
|
||||
static function onNGScoresFetch():Void
|
||||
{
|
||||
scoreboardsLoaded = true;
|
||||
|
||||
ngScoresLoaded.dispatch();
|
||||
/*
|
||||
for (score in NG.core.scoreBoards.get(8737).scores)
|
||||
{
|
||||
trace('score loaded user:${score.user.name}, score:${score.formatted_value}');
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
// var board = NG.core.scoreBoards.get(8004);// ID found in NG project view
|
||||
// board.postScore(HighScore.score);
|
||||
|
||||
// NGUtil.scoreboardArray = NG.core.scoreBoards.get(8004).scores;
|
||||
}
|
||||
#end
|
||||
}
|
|
@ -1,298 +0,0 @@
|
|||
package funkin.api.newgrounds;
|
||||
|
||||
#if newgrounds
|
||||
import flixel.util.FlxSignal;
|
||||
import io.newgrounds.NG;
|
||||
import io.newgrounds.NGLite;
|
||||
import io.newgrounds.objects.Error;
|
||||
import io.newgrounds.objects.Score;
|
||||
import lime.app.Application;
|
||||
#end
|
||||
|
||||
/**
|
||||
* MADE BY GEOKURELI THE LEGENED GOD HERO MVP
|
||||
*/
|
||||
class NGio
|
||||
{
|
||||
#if newgrounds
|
||||
/**
|
||||
* True, if the saved sessionId was used in the initial login, and failed to connect.
|
||||
* Used in MainMenuState to show a popup to establish a new connection
|
||||
*/
|
||||
public static var savedSessionFailed(default, null):Bool = false;
|
||||
|
||||
public static var scoreboardsLoaded:Bool = false;
|
||||
public static var isLoggedIn(get, never):Bool;
|
||||
|
||||
inline static function get_isLoggedIn()
|
||||
{
|
||||
return NG.core != null && NG.core.loggedIn;
|
||||
}
|
||||
|
||||
public static var scoreboardArray:Array<Score> = [];
|
||||
|
||||
public static var ngDataLoaded(default, null):FlxSignal = new FlxSignal();
|
||||
public static var ngScoresLoaded(default, null):FlxSignal = new FlxSignal();
|
||||
|
||||
public static var GAME_VER:String = "";
|
||||
|
||||
static public function checkVersion(callback:String->Void)
|
||||
{
|
||||
trace('checking NG.io version');
|
||||
GAME_VER = "v" + Application.current.meta.get('version');
|
||||
|
||||
NG.core.calls.app.getCurrentVersion(GAME_VER).addDataHandler(function(response) {
|
||||
GAME_VER = response.result.data.currentVersion;
|
||||
trace('CURRENT NG VERSION: ' + GAME_VER);
|
||||
callback(GAME_VER);
|
||||
}).send();
|
||||
}
|
||||
|
||||
static public function init()
|
||||
{
|
||||
var api = APIStuff.API;
|
||||
if (api == null || api.length == 0)
|
||||
{
|
||||
trace("Missing Newgrounds API key, aborting connection");
|
||||
return;
|
||||
}
|
||||
trace("connecting to newgrounds");
|
||||
|
||||
#if NG_FORCE_EXPIRED_SESSION
|
||||
var sessionId:String = "fake_session_id";
|
||||
function onSessionFail(error:Error)
|
||||
{
|
||||
trace("Forcing an expired saved session. " + "To disable, comment out NG_FORCE_EXPIRED_SESSION in Project.xml");
|
||||
savedSessionFailed = true;
|
||||
}
|
||||
#else
|
||||
var sessionId:String = NGLite.getSessionId();
|
||||
if (sessionId != null) trace("found web session id");
|
||||
|
||||
#if (debug)
|
||||
if (sessionId == null && APIStuff.SESSION != null)
|
||||
{
|
||||
trace("using debug session id");
|
||||
sessionId = APIStuff.SESSION;
|
||||
}
|
||||
#end
|
||||
|
||||
var onSessionFail:Error->Void = null;
|
||||
if (sessionId == null && Save.instance.ngSessionId != null)
|
||||
{
|
||||
trace("using stored session id");
|
||||
sessionId = Save.instance.ngSessionId;
|
||||
onSessionFail = function(error) savedSessionFailed = true;
|
||||
}
|
||||
#end
|
||||
|
||||
NG.create(api, sessionId, #if NG_DEBUG true #else false #end, onSessionFail);
|
||||
|
||||
#if NG_VERBOSE
|
||||
NG.core.verbose = true;
|
||||
#end
|
||||
// Set the encryption cipher/format to RC4/Base64. AES128 and Hex are not implemented yet
|
||||
NG.core.initEncryption(APIStuff.EncKey); // Found in you NG project view
|
||||
|
||||
if (NG.core.attemptingLogin)
|
||||
{
|
||||
/* a session_id was found in the loadervars, this means the user is playing on newgrounds.com
|
||||
* and we should login shortly. lets wait for that to happen
|
||||
*/
|
||||
trace("attempting login");
|
||||
NG.core.onLogin.add(onNGLogin);
|
||||
}
|
||||
// GK: taking out auto login, adding a login button to the main menu
|
||||
// else
|
||||
// {
|
||||
// /* They are NOT playing on newgrounds.com, no session id was found. We must start one manually, if we want to.
|
||||
// * Note: This will cause a new browser window to pop up where they can log in to newgrounds
|
||||
// */
|
||||
// NG.core.requestLogin(onNGLogin);
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to log in to newgrounds by requesting a new session ID, only call if no session ID was found automatically
|
||||
* @param popupLauncher The function to call to open the login url, must be inside
|
||||
* a user input event or the popup blocker will block it.
|
||||
* @param onComplete A callback with the result of the connection.
|
||||
*/
|
||||
static public function login(?popupLauncher:(Void->Void)->Void, onComplete:ConnectionResult->Void)
|
||||
{
|
||||
trace("Logging in manually");
|
||||
var onPending:Void->Void = null;
|
||||
if (popupLauncher != null)
|
||||
{
|
||||
onPending = function() popupLauncher(NG.core.openPassportUrl);
|
||||
}
|
||||
|
||||
var onSuccess:Void->Void = onNGLogin;
|
||||
var onFail:Error->Void = null;
|
||||
var onCancel:Void->Void = null;
|
||||
if (onComplete != null)
|
||||
{
|
||||
onSuccess = function() {
|
||||
onNGLogin();
|
||||
onComplete(Success);
|
||||
}
|
||||
onFail = function(e) onComplete(Fail(e.message));
|
||||
onCancel = function() onComplete(Cancelled);
|
||||
}
|
||||
|
||||
NG.core.requestLogin(onSuccess, onPending, onFail, onCancel);
|
||||
}
|
||||
|
||||
inline static public function cancelLogin():Void
|
||||
{
|
||||
NG.core.cancelLoginRequest();
|
||||
}
|
||||
|
||||
static function onNGLogin():Void
|
||||
{
|
||||
trace('logged in! user:${NG.core.user.name}');
|
||||
Save.instance.ngSessionId = NG.core.sessionId;
|
||||
Save.instance.flush();
|
||||
// Load medals then call onNGMedalFetch()
|
||||
NG.core.requestMedals(onNGMedalFetch);
|
||||
|
||||
// Load Scoreboards hten call onNGBoardsFetch()
|
||||
NG.core.requestScoreBoards(onNGBoardsFetch);
|
||||
|
||||
ngDataLoaded.dispatch();
|
||||
}
|
||||
|
||||
static public function logout()
|
||||
{
|
||||
NG.core.logOut();
|
||||
|
||||
Save.instance.ngSessionId = null;
|
||||
Save.instance.flush();
|
||||
}
|
||||
|
||||
// --- MEDALS
|
||||
static function onNGMedalFetch():Void
|
||||
{
|
||||
/*
|
||||
// Reading medal info
|
||||
for (id in NG.core.medals.keys())
|
||||
{
|
||||
var medal = NG.core.medals.get(id);
|
||||
trace('loaded medal id:$id, name:${medal.name}, description:${medal.description}');
|
||||
}
|
||||
|
||||
// Unlocking medals
|
||||
var unlockingMedal = NG.core.medals.get(54352);// medal ids are listed in your NG project viewer
|
||||
if (!unlockingMedal.unlocked)
|
||||
unlockingMedal.sendUnlock();
|
||||
*/
|
||||
}
|
||||
|
||||
// --- SCOREBOARDS
|
||||
static function onNGBoardsFetch():Void
|
||||
{
|
||||
/*
|
||||
// Reading medal info
|
||||
for (id in NG.core.scoreBoards.keys())
|
||||
{
|
||||
var board = NG.core.scoreBoards.get(id);
|
||||
trace('loaded scoreboard id:$id, name:${board.name}');
|
||||
}
|
||||
*/
|
||||
// var board = NG.core.scoreBoards.get(8004);// ID found in NG project view
|
||||
|
||||
// Posting a score thats OVER 9000!
|
||||
// board.postScore(FlxG.random.int(0, 1000));
|
||||
|
||||
// --- To view the scores you first need to select the range of scores you want to see ---
|
||||
|
||||
// add an update listener so we know when we get the new scores
|
||||
// board.onUpdate.add(onNGScoresFetch);
|
||||
trace("shoulda got score by NOW!");
|
||||
// board.requestScores(20);// get the best 10 scores ever logged
|
||||
// more info on scores --- http://www.newgrounds.io/help/components/#scoreboard-getscores
|
||||
}
|
||||
|
||||
static function onNGScoresFetch():Void
|
||||
{
|
||||
scoreboardsLoaded = true;
|
||||
|
||||
ngScoresLoaded.dispatch();
|
||||
/*
|
||||
for (score in NG.core.scoreBoards.get(8737).scores)
|
||||
{
|
||||
trace('score loaded user:${score.user.name}, score:${score.formatted_value}');
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
// var board = NG.core.scoreBoards.get(8004);// ID found in NG project view
|
||||
// board.postScore(HighScore.score);
|
||||
|
||||
// NGio.scoreboardArray = NG.core.scoreBoards.get(8004).scores;
|
||||
}
|
||||
#end
|
||||
|
||||
static public function logEvent(event:String)
|
||||
{
|
||||
#if newgrounds
|
||||
NG.core.calls.event.logEvent(event).send();
|
||||
trace('should have logged: ' + event);
|
||||
#else
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
trace('event:$event - not logged, missing NG.io lib');
|
||||
#end
|
||||
#end
|
||||
}
|
||||
|
||||
static public function unlockMedal(id:Int)
|
||||
{
|
||||
#if newgrounds
|
||||
if (isLoggedIn)
|
||||
{
|
||||
var medal = NG.core.medals.get(id);
|
||||
if (!medal.unlocked) medal.sendUnlock();
|
||||
}
|
||||
#else
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
trace('medal:$id - not unlocked, missing NG.io lib');
|
||||
#end
|
||||
#end
|
||||
}
|
||||
|
||||
static public function postScore(score:Int = 0, song:String)
|
||||
{
|
||||
#if newgrounds
|
||||
if (isLoggedIn)
|
||||
{
|
||||
for (id in NG.core.scoreBoards.keys())
|
||||
{
|
||||
var board = NG.core.scoreBoards.get(id);
|
||||
|
||||
if (song == board.name)
|
||||
{
|
||||
board.postScore(score, "Uhh meow?");
|
||||
}
|
||||
|
||||
// trace('loaded scoreboard id:$id, name:${board.name}');
|
||||
}
|
||||
}
|
||||
#else
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
trace('Song:$song, Score:$score - not posted, missing NG.io lib');
|
||||
#end
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
||||
enum ConnectionResult
|
||||
{
|
||||
/** Log in successful */
|
||||
Success;
|
||||
|
||||
/** Could not login */
|
||||
Fail(msg:String);
|
||||
|
||||
/** User cancelled the login */
|
||||
Cancelled;
|
||||
}
|
331
source/funkin/api/newgrounds/NewgroundsClient.hx
Normal file
331
source/funkin/api/newgrounds/NewgroundsClient.hx
Normal file
|
@ -0,0 +1,331 @@
|
|||
package funkin.api.newgrounds;
|
||||
|
||||
import funkin.save.Save;
|
||||
import funkin.api.newgrounds.Medals.Medal;
|
||||
#if FEATURE_NEWGROUNDS
|
||||
import io.newgrounds.Call.CallError;
|
||||
import io.newgrounds.NG;
|
||||
import io.newgrounds.NGLite;
|
||||
import io.newgrounds.NGLite.LoginOutcome;
|
||||
import io.newgrounds.NGLite.LoginFail;
|
||||
import io.newgrounds.objects.events.Outcome;
|
||||
import io.newgrounds.utils.MedalList;
|
||||
import io.newgrounds.utils.ScoreBoardList;
|
||||
import io.newgrounds.objects.User;
|
||||
|
||||
@:nullSafety
|
||||
class NewgroundsClient
|
||||
{
|
||||
public static var instance(get, never):NewgroundsClient;
|
||||
static var _instance:Null<NewgroundsClient> = null;
|
||||
|
||||
static function get_instance():NewgroundsClient
|
||||
{
|
||||
if (NewgroundsClient._instance == null) _instance = new NewgroundsClient();
|
||||
if (NewgroundsClient._instance == null) throw "Could not initialize singleton NewgroundsClient!";
|
||||
return NewgroundsClient._instance;
|
||||
}
|
||||
|
||||
public var user(get, never):Null<User>;
|
||||
public var medals(get, never):Null<MedalList>;
|
||||
public var leaderboards(get, never):Null<ScoreBoardList>;
|
||||
|
||||
private function new()
|
||||
{
|
||||
trace('[NEWGROUNDS] Initializing client...');
|
||||
|
||||
#if FEATURE_NEWGROUNDS_DEBUG
|
||||
trace('[NEWGROUNDS] App ID: ${NewgroundsCredentials.APP_ID}');
|
||||
trace('[NEWGROUNDS] Encryption Key: ${NewgroundsCredentials.ENCRYPTION_KEY}');
|
||||
#end
|
||||
|
||||
if (!hasValidCredentials())
|
||||
{
|
||||
FlxG.log.warn("Tried to initialize Newgrounds client, but credentials are invalid!");
|
||||
return;
|
||||
}
|
||||
|
||||
var debug = #if FEATURE_NEWGROUNDS_DEBUG true #else false #end;
|
||||
NG.create(NewgroundsCredentials.APP_ID, getSessionId(), debug, onLoginResolved);
|
||||
NG.core.setupEncryption(NewgroundsCredentials.ENCRYPTION_KEY);
|
||||
}
|
||||
|
||||
public function init()
|
||||
{
|
||||
if (NG.core == null) return;
|
||||
|
||||
trace('[NEWGROUNDS] Setting up connection...');
|
||||
|
||||
#if FEATURE_NEWGROUNDS_DEBUG
|
||||
NG.core.verbose = true;
|
||||
#end
|
||||
|
||||
NG.core.onLogin.add(onLoginSuccessful);
|
||||
|
||||
if (NG.core.attemptingLogin)
|
||||
{
|
||||
// Session ID was valid and we should be logged in soon.
|
||||
trace('[NEWGROUNDS] Waiting for existing login!');
|
||||
}
|
||||
else
|
||||
{
|
||||
#if FEATURE_NEWGROUNDS_AUTOLOGIN
|
||||
// Attempt an automatic login.
|
||||
trace('[NEWGROUNDS] Attempting new login immediately!');
|
||||
this.autoLogin();
|
||||
#else
|
||||
trace('[NEWGROUNDS] Not logged in, you have to login manually!');
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to log into Newgrounds and create a session ID.
|
||||
* @param onSuccess An optional callback for when the login is successful.
|
||||
* @param onError An optional callback for when the login fails.
|
||||
*/
|
||||
public function login(?onSuccess:Void->Void, ?onError:Void->Void):Void
|
||||
{
|
||||
if (NG.core == null)
|
||||
{
|
||||
FlxG.log.warn("No Newgrounds client initialized! Are your credentials invalid?");
|
||||
return;
|
||||
}
|
||||
|
||||
if (onSuccess != null && onError != null)
|
||||
{
|
||||
NG.core.requestLogin(onLoginResolvedWithCallbacks.bind(_, onSuccess, onError));
|
||||
}
|
||||
else
|
||||
{
|
||||
NG.core.requestLogin(onLoginResolved);
|
||||
}
|
||||
}
|
||||
|
||||
public function autoLogin(?onSuccess:Void->Void, ?onError:Void->Void):Void
|
||||
{
|
||||
if (NG.core == null)
|
||||
{
|
||||
FlxG.log.warn("No Newgrounds client initialized! Are your credentials invalid?");
|
||||
return;
|
||||
}
|
||||
|
||||
var dummyPassport:String->Void = function(_) {
|
||||
// just a dummy passport, so we don't create a popup
|
||||
// otherwise `NG.core.requestLogin()` will automatically attempt to open a tab at the beginning of the game
|
||||
// users should go to the Options Menu to login to NG
|
||||
// we cancel the request, so we can call it later
|
||||
NG.core.cancelLoginRequest();
|
||||
};
|
||||
|
||||
if (onSuccess != null && onError != null)
|
||||
{
|
||||
NG.core.requestLogin(onLoginResolvedWithCallbacks.bind(_, onSuccess, onError), dummyPassport);
|
||||
}
|
||||
else
|
||||
{
|
||||
NG.core.requestLogin(onLoginResolved, dummyPassport);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log out of Newgrounds and invalidate the current session.
|
||||
* @param onSuccess An optional callback for when the logout is successful.
|
||||
*/
|
||||
public function logout(?onSuccess:Void->Void, ?onError:Void->Void):Void
|
||||
{
|
||||
if (NG.core != null)
|
||||
{
|
||||
if (onSuccess != null && onError != null)
|
||||
{
|
||||
NG.core.logOut(onLogoutResolvedWithCallbacks.bind(_, onSuccess, onError));
|
||||
}
|
||||
else
|
||||
{
|
||||
NG.core.logOut(onLogoutResolved);
|
||||
}
|
||||
}
|
||||
|
||||
Save.instance.ngSessionId = null;
|
||||
}
|
||||
|
||||
public function isLoggedIn():Bool
|
||||
{
|
||||
#if FEATURE_NEWGROUNDS
|
||||
return NG.core != null && NG.core.loggedIn;
|
||||
#else
|
||||
return false;
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns `false` if either the app ID or the encryption key is invalid.
|
||||
*/
|
||||
static function hasValidCredentials():Bool
|
||||
{
|
||||
return !(NewgroundsCredentials.APP_ID == null
|
||||
|| NewgroundsCredentials.APP_ID == ""
|
||||
|| NewgroundsCredentials.APP_ID.contains(" ")
|
||||
|| NewgroundsCredentials.ENCRYPTION_KEY == null
|
||||
|| NewgroundsCredentials.ENCRYPTION_KEY == ""
|
||||
|| NewgroundsCredentials.ENCRYPTION_KEY.contains(" "));
|
||||
}
|
||||
|
||||
function onLoginResolved(outcome:LoginOutcome):Void
|
||||
{
|
||||
switch (outcome)
|
||||
{
|
||||
case SUCCESS:
|
||||
onLoginSuccessful();
|
||||
case FAIL(result):
|
||||
onLoginFailed(result);
|
||||
}
|
||||
}
|
||||
|
||||
function onLoginResolvedWithCallbacks(outcome:LoginOutcome, onSuccess:Void->Void, onError:Void->Void):Void
|
||||
{
|
||||
onLoginResolved(outcome);
|
||||
|
||||
switch (outcome)
|
||||
{
|
||||
case SUCCESS:
|
||||
onSuccess();
|
||||
case FAIL(result):
|
||||
onError();
|
||||
}
|
||||
}
|
||||
|
||||
function onLogoutResolved(outcome:Outcome<CallError>):Void
|
||||
{
|
||||
switch (outcome)
|
||||
{
|
||||
case SUCCESS:
|
||||
onLogoutSuccessful();
|
||||
case FAIL(result):
|
||||
onLogoutFailed(result);
|
||||
}
|
||||
}
|
||||
|
||||
function onLogoutResolvedWithCallbacks(outcome:Outcome<CallError>, onSuccess:Void->Void, onError:Void->Void):Void
|
||||
{
|
||||
onLogoutResolved(outcome);
|
||||
|
||||
switch (outcome)
|
||||
{
|
||||
case SUCCESS:
|
||||
onSuccess();
|
||||
case FAIL(result):
|
||||
onError();
|
||||
}
|
||||
}
|
||||
|
||||
function onLoginSuccessful():Void
|
||||
{
|
||||
if (NG.core == null) return;
|
||||
|
||||
trace('[NEWGROUNDS] Login successful!');
|
||||
|
||||
// Persist the session ID.
|
||||
Save.instance.ngSessionId = NG.core.sessionId;
|
||||
|
||||
trace('[NEWGROUNDS] Submitting medal request...');
|
||||
NG.core.requestMedals(onFetchedMedals);
|
||||
|
||||
trace('[NEWGROUNDS] Submitting leaderboard request...');
|
||||
NG.core.scoreBoards.loadList(onFetchedLeaderboards);
|
||||
}
|
||||
|
||||
function onLoginFailed(result:LoginFail):Void
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case CANCELLED(type):
|
||||
switch (type)
|
||||
{
|
||||
case PASSPORT:
|
||||
trace('[NEWGROUNDS] Login cancelled by passport website.');
|
||||
case MANUAL:
|
||||
trace('[NEWGROUNDS] Login cancelled by application.');
|
||||
default:
|
||||
trace('[NEWGROUNDS] Login cancelled by unknown source.');
|
||||
}
|
||||
case ERROR(error):
|
||||
switch (error)
|
||||
{
|
||||
case HTTP(error):
|
||||
trace('[NEWGROUNDS] Login failed due to HTTP error: ${error}');
|
||||
case RESPONSE(error):
|
||||
trace('[NEWGROUNDS] Login failed due to response error: ${error.message} (${error.code})');
|
||||
case RESULT(error):
|
||||
trace('[NEWGROUNDS] Login failed due to result error: ${error.message} (${error.code})');
|
||||
default:
|
||||
trace('[NEWGROUNDS] Login failed due to unknown error: ${error}');
|
||||
}
|
||||
default:
|
||||
trace('[NEWGROUNDS] Login failed due to unknown reason.');
|
||||
}
|
||||
}
|
||||
|
||||
function onLogoutSuccessful():Void
|
||||
{
|
||||
trace('[NEWGROUNDS] Logout successful!');
|
||||
}
|
||||
|
||||
function onLogoutFailed(result:CallError):Void
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case HTTP(error):
|
||||
trace('[NEWGROUNDS] Logout failed due to HTTP error: ${error}');
|
||||
case RESPONSE(error):
|
||||
trace('[NEWGROUNDS] Logout failed due to response error: ${error.message} (${error.code})');
|
||||
case RESULT(error):
|
||||
trace('[NEWGROUNDS] Logout failed due to result error: ${error.message} (${error.code})');
|
||||
default:
|
||||
trace('[NEWGROUNDS] Logout failed due to unknown error: ${result}');
|
||||
}
|
||||
}
|
||||
|
||||
function onFetchedMedals(outcome:Outcome<CallError>):Void
|
||||
{
|
||||
trace('[NEWGROUNDS] Fetched medals!');
|
||||
}
|
||||
|
||||
function onFetchedLeaderboards(outcome:Outcome<CallError>):Void
|
||||
{
|
||||
trace('[NEWGROUNDS] Fetched leaderboards!');
|
||||
|
||||
// trace(funkin.api.newgrounds.Leaderboards.listLeaderboardData());
|
||||
}
|
||||
|
||||
function get_user():Null<User>
|
||||
{
|
||||
if (NG.core == null || !this.isLoggedIn()) return null;
|
||||
return NG.core.user;
|
||||
}
|
||||
|
||||
function get_medals():Null<MedalList>
|
||||
{
|
||||
if (NG.core == null || !this.isLoggedIn()) return null;
|
||||
return NG.core.medals;
|
||||
}
|
||||
|
||||
function get_leaderboards():Null<ScoreBoardList>
|
||||
{
|
||||
if (NG.core == null || !this.isLoggedIn()) return null;
|
||||
return NG.core.scoreBoards;
|
||||
}
|
||||
|
||||
static function getSessionId():Null<String>
|
||||
{
|
||||
#if js
|
||||
// We can fetch the session ID from the URL.
|
||||
var result:Null<String> = NGLite.getSessionId();
|
||||
if (result != null) return result;
|
||||
#end
|
||||
|
||||
// We have to fetch the session ID from the save file.
|
||||
return Save.instance.ngSessionId;
|
||||
}
|
||||
}
|
||||
#end
|
|
@ -1,101 +0,0 @@
|
|||
package funkin.api.newgrounds;
|
||||
|
||||
#if newgrounds
|
||||
import funkin.NGio;
|
||||
import funkin.ui.Prompt;
|
||||
|
||||
class NgPrompt extends Prompt
|
||||
{
|
||||
public function new(text:String, style:ButtonStyle = Yes_No)
|
||||
{
|
||||
super(text, style);
|
||||
}
|
||||
|
||||
static public function showLogin()
|
||||
{
|
||||
return showLoginPrompt(true);
|
||||
}
|
||||
|
||||
static public function showSavedSessionFailed()
|
||||
{
|
||||
return showLoginPrompt(false);
|
||||
}
|
||||
|
||||
static function showLoginPrompt(fromUi:Bool)
|
||||
{
|
||||
var prompt = new NgPrompt("Talking to server...", None);
|
||||
prompt.openCallback = NGUtil.login.bind(function popupLauncher(openPassportUrl)
|
||||
{
|
||||
var choiceMsg = fromUi ? #if web "Log in to Newgrounds?" #else null #end // User-input needed to allow popups
|
||||
: "Your session has expired.\n Please login again.";
|
||||
|
||||
if (choiceMsg != null)
|
||||
{
|
||||
prompt.setText(choiceMsg);
|
||||
prompt.setButtons(Yes_No);
|
||||
#if web
|
||||
prompt.buttons.getItem("yes").fireInstantly = true;
|
||||
#end
|
||||
prompt.onYes = function() {
|
||||
prompt.setText("Connecting..." #if web + "\n(check your popup blocker)" #end);
|
||||
prompt.setButtons(None);
|
||||
openPassportUrl();
|
||||
};
|
||||
prompt.onNo = function() {
|
||||
prompt.close();
|
||||
prompt = null;
|
||||
NGio.cancelLogin();
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
prompt.setText("Connecting...");
|
||||
openPassportUrl();
|
||||
}
|
||||
}, function onLoginComplete(result:ConnectionResult)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case Success:
|
||||
{
|
||||
prompt.setText("Login Successful");
|
||||
prompt.setButtons(Ok);
|
||||
prompt.onYes = prompt.close;
|
||||
}
|
||||
case Fail(msg):
|
||||
{
|
||||
trace("Login Error:" + msg);
|
||||
prompt.setText("Login failed");
|
||||
prompt.setButtons(Ok);
|
||||
prompt.onYes = prompt.close;
|
||||
}
|
||||
case Cancelled:
|
||||
{
|
||||
if (prompt != null)
|
||||
{
|
||||
prompt.setText("Login cancelled by user");
|
||||
prompt.setButtons(Ok);
|
||||
prompt.onYes = prompt.close;
|
||||
}
|
||||
else
|
||||
trace("Login cancelled via prompt");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return prompt;
|
||||
}
|
||||
|
||||
static public function showLogout()
|
||||
{
|
||||
var user = io.newgrounds.NG.core.user.name;
|
||||
var prompt = new NgPrompt('Log out of $user?', Yes_No);
|
||||
prompt.onYes = function() {
|
||||
NGio.logout();
|
||||
prompt.close();
|
||||
};
|
||||
prompt.onNo = prompt.close;
|
||||
return prompt;
|
||||
}
|
||||
}
|
||||
#end
|
|
@ -1,9 +0,0 @@
|
|||
# funkin.api.newgrounds
|
||||
|
||||
This package contains two main classes:
|
||||
- `NGUtil` contains utility functions for interacting with the Newgrounds API.
|
||||
- This includes any functions which scripts should be able to use,
|
||||
such as retrieving achievement status.
|
||||
- `NGUnsafe` contains sensitive utility functions for interacting with the Newgrounds API.
|
||||
- This includes any functions which scripts should not be able to use,
|
||||
such as writing high scores or posting achievements.
|
|
@ -1,9 +1,6 @@
|
|||
package funkin.audio;
|
||||
|
||||
import flash.media.Sound;
|
||||
#if flash11
|
||||
import flash.utils.ByteArray;
|
||||
#end
|
||||
import flixel.sound.FlxSound;
|
||||
import flixel.system.FlxAssets.FlxSoundAsset;
|
||||
import openfl.Assets;
|
||||
|
|
|
@ -13,8 +13,6 @@ import funkin.data.song.SongRegistry;
|
|||
import funkin.util.tools.ICloneable;
|
||||
import funkin.util.flixel.sound.FlxPartialSound;
|
||||
import funkin.Paths.PathsFunction;
|
||||
import openfl.Assets;
|
||||
import lime.app.Future;
|
||||
import lime.app.Promise;
|
||||
import openfl.media.SoundMixer;
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package funkin.audio;
|
||||
|
||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
import funkin.audio.FunkinSound;
|
||||
import flixel.tweens.FlxTween;
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,60 +1,76 @@
|
|||
package funkin.audio.visualize;
|
||||
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
|
||||
import flixel.sound.FlxSound;
|
||||
import funkin.vis.dsp.SpectralAnalyzer;
|
||||
import funkin.vis.audioclip.frontends.LimeAudioClip;
|
||||
|
||||
using Lambda;
|
||||
|
||||
@:nullSafety
|
||||
class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
|
||||
{
|
||||
// public var vis:VisShit;
|
||||
var analyzer:SpectralAnalyzer;
|
||||
var analyzer:Null<SpectralAnalyzer> = null;
|
||||
|
||||
var volumes:Array<Float> = [];
|
||||
|
||||
public var snd:FlxSound;
|
||||
public var snd:Null<FlxSound> = null;
|
||||
|
||||
public function new(snd:FlxSound)
|
||||
static final BAR_COUNT:Int = 7;
|
||||
|
||||
// TODO: Make the sprites easier to soft code.
|
||||
public function new(snd:FlxSound, pixel:Bool)
|
||||
{
|
||||
super();
|
||||
|
||||
this.snd = snd;
|
||||
|
||||
// vis = new VisShit(snd);
|
||||
// vis.snd = snd;
|
||||
var visCount = pixel ? (BAR_COUNT + 1) : (BAR_COUNT + 1);
|
||||
var visScale = pixel ? 6 : 1;
|
||||
|
||||
var visFrms:FlxAtlasFrames = Paths.getSparrowAtlas('aBotViz');
|
||||
var visFrms:FlxAtlasFrames = Paths.getSparrowAtlas(pixel ? 'characters/abotPixel/aBotVizPixel' : 'characters/abot/aBotViz');
|
||||
|
||||
// these are the differences in X position, from left to right
|
||||
var positionX:Array<Float> = [0, 59, 56, 66, 54, 52, 51];
|
||||
var positionY:Array<Float> = [0, -8, -3.5, -0.4, 0.5, 4.7, 7];
|
||||
var positionX:Array<Float> = pixel ? [0, 7 * 6, 8 * 6, 9 * 6, 10 * 6, 6 * 6, 7 * 6] : [0, 59, 56, 66, 54, 52, 51];
|
||||
var positionY:Array<Float> = pixel ? [0, -2 * 6, -1 * 6, 0, 0, 1 * 6, 2 * 6] : [0, -8, -3.5, -0.4, 0.5, 4.7, 7];
|
||||
|
||||
for (lol in 1...8)
|
||||
for (index in 1...visCount)
|
||||
{
|
||||
// pushes initial value
|
||||
volumes.push(0.0);
|
||||
var sum = function(num:Float, total:Float) return total += num;
|
||||
var posX:Float = positionX.slice(0, lol).fold(sum, 0);
|
||||
var posY:Float = positionY.slice(0, lol).fold(sum, 0);
|
||||
|
||||
var viz:FlxSprite = new FlxSprite(posX, posY);
|
||||
// Sum the offsets up to the current index
|
||||
var sum = function(num:Float, total:Float) return total += num;
|
||||
var posX:Float = positionX.slice(0, index).fold(sum, 0);
|
||||
var posY:Float = positionY.slice(0, index).fold(sum, 0);
|
||||
|
||||
var viz:FunkinSprite = new FunkinSprite(posX, posY);
|
||||
viz.frames = visFrms;
|
||||
viz.antialiasing = pixel ? false : true;
|
||||
viz.scale.set(visScale, visScale);
|
||||
add(viz);
|
||||
|
||||
var visStr = 'viz';
|
||||
viz.animation.addByPrefix('VIZ', visStr + lol, 0);
|
||||
viz.animation.play('VIZ', false, false, 6);
|
||||
viz.animation.addByPrefix('VIZ', '$visStr${index}0', 0);
|
||||
viz.animation.play('VIZ', false, false, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public function initAnalyzer()
|
||||
public function initAnalyzer():Void
|
||||
{
|
||||
if (snd == null) return;
|
||||
|
||||
@:privateAccess
|
||||
analyzer = new SpectralAnalyzer(snd._channel.__audioSource, 7, 0.1, 40);
|
||||
analyzer = new SpectralAnalyzer(snd._channel.__audioSource, BAR_COUNT, 0.1, 40);
|
||||
// A-Bot tuning...
|
||||
analyzer.minDb = -65;
|
||||
analyzer.maxDb = -25;
|
||||
analyzer.maxFreq = 22000;
|
||||
// we use a very low minFreq since some songs use low low subbass like a boss
|
||||
analyzer.minFreq = 10;
|
||||
|
||||
#if desktop
|
||||
// On desktop it uses FFT stuff that isn't as optimized as the direct browser stuff we use on HTML5
|
||||
|
@ -66,16 +82,17 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
|
|||
// analyzer.fftN = 2048;
|
||||
}
|
||||
|
||||
public function dumpSound():Void
|
||||
{
|
||||
snd = null;
|
||||
analyzer = null;
|
||||
}
|
||||
|
||||
var visTimer:Float = -1;
|
||||
var visTimeMax:Float = 1 / 30;
|
||||
|
||||
override function update(elapsed:Float)
|
||||
{
|
||||
// updateViz();
|
||||
|
||||
// updateFFT(elapsed);
|
||||
|
||||
//
|
||||
super.update(elapsed);
|
||||
}
|
||||
|
||||
|
@ -86,8 +103,8 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
|
|||
|
||||
override function draw()
|
||||
{
|
||||
if (analyzer != null) drawFFT();
|
||||
super.draw();
|
||||
drawFFT();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -95,17 +112,17 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
|
|||
*/
|
||||
function drawFFT():Void
|
||||
{
|
||||
var levels = analyzer.getLevels();
|
||||
var levels = (analyzer != null) ? analyzer.getLevels() : getDefaultLevels();
|
||||
|
||||
for (i in 0...min(group.members.length, levels.length))
|
||||
{
|
||||
var animFrame:Int = Math.round(levels[i].value * 5);
|
||||
var animFrame:Int = Math.round(levels[i].value * 6);
|
||||
|
||||
#if desktop
|
||||
// Web version scales with the Flixel volume level.
|
||||
// This line brings platform parity but looks worse.
|
||||
// animFrame = Math.round(animFrame * FlxG.sound.volume);
|
||||
#end
|
||||
// don't display if we're at 0 volume from the level
|
||||
group.members[i].visible = animFrame > 0;
|
||||
|
||||
// decrement our animFrame, so we can get a value from 0-5 for animation frames
|
||||
animFrame -= 1;
|
||||
|
||||
animFrame = Math.floor(Math.min(5, animFrame));
|
||||
animFrame = Math.floor(Math.max(0, animFrame));
|
||||
|
@ -116,79 +133,19 @@ class ABotVis extends FlxTypedSpriteGroup<FlxSprite>
|
|||
}
|
||||
}
|
||||
|
||||
// function updateFFT(elapsed:Float)
|
||||
// {
|
||||
// if (vis.snd != null)
|
||||
// {
|
||||
// vis.checkAndSetBuffer();
|
||||
// if (vis.setBuffer)
|
||||
// {
|
||||
// var remappedShit:Int = 0;
|
||||
// if (vis.snd.playing) remappedShit = Std.int(FlxMath.remapToRange(vis.snd.time, 0, vis.snd.length, 0, vis.numSamples));
|
||||
// else
|
||||
// remappedShit = Std.int(FlxMath.remapToRange(Conductor.instance.songPosition, 0, vis.snd.length, 0, vis.numSamples));
|
||||
// var fftSamples:Array<Float> = [];
|
||||
// var swagBucks = remappedShit;
|
||||
// for (i in remappedShit...remappedShit + (Std.int((44100 * (1 / 144)))))
|
||||
// {
|
||||
// var left = vis.audioData[swagBucks] / 32767;
|
||||
// var right = vis.audioData[swagBucks + 1] / 32767;
|
||||
// var balanced = (left + right) / 2;
|
||||
// swagBucks += 2;
|
||||
// fftSamples.push(balanced);
|
||||
// }
|
||||
// var freqShit = vis.funnyFFT(fftSamples);
|
||||
// for (i in 0...group.members.length)
|
||||
// {
|
||||
// var getSliceShit = function(s:Int) {
|
||||
// var powShit = FlxMath.remapToRange(s, 0, group.members.length, 0, MathUtil.logBase(10, freqShit[0].length));
|
||||
// return Math.round(Math.pow(10, powShit));
|
||||
// };
|
||||
// // var powShit:Float = getSliceShit(i);
|
||||
// var hzSliced:Int = getSliceShit(i);
|
||||
// var sliceLength:Int = Std.int(freqShit[0].length / group.members.length);
|
||||
// var volSlice = freqShit[0].slice(hzSliced, getSliceShit(i + 1));
|
||||
// var avgVel:Float = 0;
|
||||
// for (slice in volSlice)
|
||||
// {
|
||||
// avgVel += slice;
|
||||
// }
|
||||
// avgVel /= volSlice.length;
|
||||
// avgVel *= 10000000;
|
||||
// volumes[i] += avgVel - (elapsed * (volumes[i] * 50));
|
||||
// var animFrame:Int = Std.int(volumes[i]);
|
||||
// animFrame = Math.floor(Math.min(5, animFrame));
|
||||
// animFrame = Math.floor(Math.max(0, animFrame));
|
||||
// animFrame = Std.int(Math.abs(animFrame - 5)); // shitty dumbass flip, cuz dave got da shit backwards lol!
|
||||
// group.members[i].animation.curAnim.curFrame = animFrame;
|
||||
// if (FlxG.keys.justPressed.U)
|
||||
// {
|
||||
// trace(avgVel);
|
||||
// trace(group.members[i].animation.curAnim.curFrame);
|
||||
// }
|
||||
// }
|
||||
// // group.members[0].animation.curAnim.curFrame =
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// public function updateViz()
|
||||
// {
|
||||
// if (vis.snd != null)
|
||||
// {
|
||||
// var remappedShit:Int = 0;
|
||||
// vis.checkAndSetBuffer();
|
||||
// if (vis.setBuffer)
|
||||
// {
|
||||
// // var startingSample:Int = Std.int(FlxMath.remapToRange)
|
||||
// if (vis.snd.playing) remappedShit = Std.int(FlxMath.remapToRange(vis.snd.time, 0, vis.snd.length, 0, vis.numSamples));
|
||||
// for (i in 0...group.members.length)
|
||||
// {
|
||||
// var sampleApprox:Int = Std.int(FlxMath.remapToRange(i, 0, group.members.length, remappedShit, remappedShit + 500));
|
||||
// var left = vis.audioData[sampleApprox] / 32767;
|
||||
// var animFrame:Int = Std.int(FlxMath.remapToRange(left, -1, 1, 0, 6));
|
||||
// group.members[i].animation.curAnim.curFrame = animFrame;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
/**
|
||||
* Explicitly define the default levels to draw when the analyzer is not available.
|
||||
* @return Array<Bar>
|
||||
*/
|
||||
static function getDefaultLevels():Array<Bar>
|
||||
{
|
||||
var result:Array<Bar> = [];
|
||||
|
||||
for (i in 0...BAR_COUNT)
|
||||
{
|
||||
result.push({value: 0, peak: 0.0});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import flixel.math.FlxMath;
|
|||
import flixel.math.FlxPoint;
|
||||
import flixel.sound.FlxSound;
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.audio.visualize.VisShit;
|
||||
import funkin.audio.visualize.VisShit.CurAudioInfo;
|
||||
import funkin.graphics.rendering.MeshRender;
|
||||
import lime.utils.Int16Array;
|
||||
|
||||
|
|
|
@ -17,6 +17,16 @@ class AnimationDataUtil
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param data
|
||||
* @param name (adds index to name)
|
||||
* @return Array<AnimationData>
|
||||
*/
|
||||
public static function toNamedArray(data:Array<UnnamedAnimationData>, ?name:String = ""):Array<AnimationData>
|
||||
{
|
||||
return data.mapi(function(animItem, ind) return toNamed(animItem, '$name$ind'));
|
||||
}
|
||||
|
||||
public static function toUnnamed(data:AnimationData):UnnamedAnimationData
|
||||
{
|
||||
return {
|
||||
|
@ -30,6 +40,11 @@ class AnimationDataUtil
|
|||
frameIndices: data.frameIndices
|
||||
};
|
||||
}
|
||||
|
||||
public static function toUnnamedArray(data:Array<AnimationData>):Array<UnnamedAnimationData>
|
||||
{
|
||||
return data.map(toUnnamed);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -13,7 +13,7 @@ class DialogueBoxRegistry extends BaseRegistry<DialogueBox, DialogueBoxData>
|
|||
*/
|
||||
public static final DIALOGUEBOX_DATA_VERSION:thx.semver.Version = "1.1.0";
|
||||
|
||||
public static final DIALOGUEBOX_DATA_VERSION_RULE:thx.semver.VersionRule = "1.1.x";
|
||||
public static final DIALOGUEBOX_DATA_VERSION_RULE:thx.semver.VersionRule = ">=1.0.0 <1.2.0";
|
||||
|
||||
public static var instance(get, never):DialogueBoxRegistry;
|
||||
static var _instance:Null<DialogueBoxRegistry> = null;
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package funkin.data.dialogue.speaker;
|
||||
|
||||
import funkin.play.cutscene.dialogue.Speaker;
|
||||
import funkin.data.dialogue.speaker.SpeakerData;
|
||||
import funkin.play.cutscene.dialogue.ScriptedSpeaker;
|
||||
|
||||
class SpeakerRegistry extends BaseRegistry<Speaker, SpeakerData>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package funkin.data.event;
|
||||
|
||||
import funkin.play.event.SongEvent;
|
||||
import funkin.data.event.SongEventSchema;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
import funkin.util.macro.ClassMacro;
|
||||
import funkin.play.event.ScriptedSongEvent;
|
||||
|
|
|
@ -1,11 +1,5 @@
|
|||
package funkin.data.event;
|
||||
|
||||
import funkin.play.event.SongEvent;
|
||||
import funkin.data.event.SongEventSchema;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
import funkin.util.macro.ClassMacro;
|
||||
import funkin.play.event.ScriptedSongEvent;
|
||||
|
||||
@:forward(name, title, type, keys, min, max, step, units, defaultValue, iterator)
|
||||
abstract SongEventSchema(SongEventSchemaRaw)
|
||||
{
|
||||
|
|
|
@ -331,6 +331,10 @@ typedef PlayerResultsAnimationData =
|
|||
@:default([0, 0])
|
||||
var offsets:Array<Float>;
|
||||
|
||||
@:optional
|
||||
@:default("both")
|
||||
var filter:String;
|
||||
|
||||
@:optional
|
||||
@:default(500)
|
||||
var zIndex:Int;
|
||||
|
@ -347,6 +351,10 @@ typedef PlayerResultsAnimationData =
|
|||
@:default('')
|
||||
var startFrameLabel:Null<String>;
|
||||
|
||||
@:optional
|
||||
@:default('')
|
||||
var sound:Null<String>;
|
||||
|
||||
@:optional
|
||||
@:default(true)
|
||||
var looped:Bool;
|
||||
|
|
|
@ -5,6 +5,8 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
<!-- TODO: Include NoteSplash and NoteStyle changes made to accomodate week 6 note splash/hold stuff -->
|
||||
|
||||
## [1.1.0]
|
||||
### Added
|
||||
- Added several new `assets`:
|
||||
|
|
|
@ -187,6 +187,10 @@ typedef NoteStyleAssetData<T> =
|
|||
@:optional
|
||||
var isPixel:Bool;
|
||||
|
||||
@:default(1.0)
|
||||
@:optional
|
||||
var alpha:Float;
|
||||
|
||||
/**
|
||||
* If true, animations will be played on the graphic.
|
||||
* @default `false` to save performance.
|
||||
|
@ -251,6 +255,30 @@ typedef NoteStyleData_NoteSplash =
|
|||
@:optional
|
||||
@:default(true)
|
||||
var enabled:Bool;
|
||||
|
||||
@:optional
|
||||
@:default(24)
|
||||
var framerateDefault:Int;
|
||||
|
||||
@:optional
|
||||
@:default(2)
|
||||
var framerateVariance:Int;
|
||||
|
||||
@:optional
|
||||
@:default("normal")
|
||||
var blendMode:String;
|
||||
|
||||
@:optional
|
||||
var leftSplashes:Array<UnnamedAnimationData>;
|
||||
|
||||
@:optional
|
||||
var downSplashes:Array<UnnamedAnimationData>;
|
||||
|
||||
@:optional
|
||||
var upSplashes:Array<UnnamedAnimationData>;
|
||||
|
||||
@:optional
|
||||
var rightSplashes:Array<UnnamedAnimationData>;
|
||||
};
|
||||
|
||||
typedef NoteStyleData_HoldNoteCover =
|
||||
|
@ -262,4 +290,33 @@ typedef NoteStyleData_HoldNoteCover =
|
|||
@:optional
|
||||
@:default(true)
|
||||
var enabled:Bool;
|
||||
|
||||
@:optional
|
||||
var left:NoteStyleData_HoldNoteCoverDirectionData;
|
||||
|
||||
@:optional
|
||||
var down:NoteStyleData_HoldNoteCoverDirectionData;
|
||||
|
||||
@:optional
|
||||
var up:NoteStyleData_HoldNoteCoverDirectionData;
|
||||
|
||||
@:optional
|
||||
var right:NoteStyleData_HoldNoteCoverDirectionData;
|
||||
};
|
||||
|
||||
typedef NoteStyleData_HoldNoteCoverDirectionData =
|
||||
{
|
||||
/**
|
||||
* Optionally specify an asset path to use for this specific animation.
|
||||
* @:default The assetPath of the main holdNoteCover asset
|
||||
*/
|
||||
@:optional
|
||||
var assetPath:String;
|
||||
|
||||
@:optional
|
||||
var start:UnnamedAnimationData;
|
||||
@:optional
|
||||
var hold:UnnamedAnimationData;
|
||||
@:optional
|
||||
var end:UnnamedAnimationData;
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ class NoteStyleRegistry extends BaseRegistry<NoteStyle, NoteStyleData>
|
|||
*/
|
||||
public static final NOTE_STYLE_DATA_VERSION:thx.semver.Version = "1.1.0";
|
||||
|
||||
public static final NOTE_STYLE_DATA_VERSION_RULE:thx.semver.VersionRule = "1.1.x";
|
||||
public static final NOTE_STYLE_DATA_VERSION_RULE:thx.semver.VersionRule = ">=1.0.0 <1.2.0";
|
||||
|
||||
public static var instance(get, never):NoteStyleRegistry;
|
||||
static var _instance:Null<NoteStyleRegistry> = null;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package funkin.data.song;
|
||||
|
||||
import funkin.data.freeplay.player.PlayerRegistry;
|
||||
import funkin.data.song.SongData;
|
||||
import funkin.data.song.migrator.SongData_v2_0_0.SongMetadata_v2_0_0;
|
||||
import funkin.data.song.migrator.SongData_v2_1_0.SongMetadata_v2_1_0;
|
||||
|
@ -171,7 +172,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
|||
|
||||
public function parseEntryMetadataWithMigration(id:String, variation:String, version:thx.semver.Version):Null<SongMetadata>
|
||||
{
|
||||
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
|
||||
variation = variation ?? Constants.DEFAULT_VARIATION;
|
||||
|
||||
// If a version rule is not specified, do not check against it.
|
||||
if (SONG_METADATA_VERSION_RULE == null || VersionUtil.validateVersion(version, SONG_METADATA_VERSION_RULE))
|
||||
|
@ -192,12 +193,13 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
|||
}
|
||||
}
|
||||
|
||||
public function parseEntryMetadataRawWithMigration(contents:String, ?fileName:String = 'raw', version:thx.semver.Version):Null<SongMetadata>
|
||||
public function parseEntryMetadataRawWithMigration(contents:String, ?fileName:String = 'raw', version:thx.semver.Version,
|
||||
?variation:String):Null<SongMetadata>
|
||||
{
|
||||
// If a version rule is not specified, do not check against it.
|
||||
if (SONG_METADATA_VERSION_RULE == null || VersionUtil.validateVersion(version, SONG_METADATA_VERSION_RULE))
|
||||
{
|
||||
return parseEntryMetadataRaw(contents, fileName);
|
||||
return parseEntryMetadataRaw(contents, fileName, variation);
|
||||
}
|
||||
else if (VersionUtil.validateVersion(version, "2.1.x"))
|
||||
{
|
||||
|
@ -546,4 +548,39 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
|||
return listBaseGameSongIds().indexOf(id) == -1;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of all difficulties for a specific character.
|
||||
*/
|
||||
public function listAllDifficulties(characterId:String):Array<String>
|
||||
{
|
||||
var allDifficulties:Array<String> = Constants.DEFAULT_DIFFICULTY_LIST.copy();
|
||||
var character = PlayerRegistry.instance.fetchEntry(characterId);
|
||||
|
||||
if (character == null)
|
||||
{
|
||||
trace(' [WARN] Could not locate character $characterId');
|
||||
return allDifficulties;
|
||||
}
|
||||
|
||||
allDifficulties = [];
|
||||
for (songId in listEntryIds())
|
||||
{
|
||||
var song = fetchEntry(songId);
|
||||
if (song == null) continue;
|
||||
|
||||
for (diff in song.listDifficulties(null, song.getVariationsByCharacter(character)))
|
||||
{
|
||||
if (!allDifficulties.contains(diff)) allDifficulties.push(diff);
|
||||
}
|
||||
}
|
||||
|
||||
if (allDifficulties.length == 0)
|
||||
{
|
||||
trace(' [WARN] No difficulties found. Returning default difficulty list.');
|
||||
allDifficulties = Constants.DEFAULT_DIFFICULTY_LIST.copy();
|
||||
}
|
||||
|
||||
return allDifficulties;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -173,10 +173,10 @@ typedef StageDataProp =
|
|||
* [1, 1] means the prop moves 1:1 with the camera.
|
||||
* [0.5, 0.5] means the prop moves half as much as the camera.
|
||||
* [0, 0] means the prop is not moved.
|
||||
* @default [0, 0]
|
||||
* @default [1, 1]
|
||||
*/
|
||||
@:optional
|
||||
@:default([0, 0])
|
||||
@:default([1, 1])
|
||||
var scroll:Array<Float>;
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package funkin.data.stage;
|
||||
|
||||
import funkin.data.stage.StageData;
|
||||
import funkin.play.stage.Stage;
|
||||
import funkin.play.stage.ScriptedStage;
|
||||
|
||||
|
@ -13,7 +12,7 @@ class StageRegistry extends BaseRegistry<Stage, StageData>
|
|||
*/
|
||||
public static final STAGE_DATA_VERSION:thx.semver.Version = "1.0.3";
|
||||
|
||||
public static final STAGE_DATA_VERSION_RULE:thx.semver.VersionRule = ">=1.0.0 <=1.0.3";
|
||||
public static final STAGE_DATA_VERSION_RULE:thx.semver.VersionRule = ">=1.0.0 <1.1.0";
|
||||
|
||||
public static var instance(get, never):StageRegistry;
|
||||
static var _instance:Null<StageRegistry> = null;
|
||||
|
|
|
@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [1.1.0]
|
||||
## [1.0.1]
|
||||
- Added `visible` attribute.
|
||||
|
||||
## [1.0.0]
|
||||
|
|
|
@ -2,7 +2,6 @@ package funkin.data.story.level;
|
|||
|
||||
import funkin.util.SortUtil;
|
||||
import funkin.ui.story.Level;
|
||||
import funkin.data.story.level.LevelData;
|
||||
import funkin.ui.story.ScriptedLevel;
|
||||
|
||||
class LevelRegistry extends BaseRegistry<Level, LevelData>
|
||||
|
@ -14,7 +13,7 @@ class LevelRegistry extends BaseRegistry<Level, LevelData>
|
|||
*/
|
||||
public static final LEVEL_DATA_VERSION:thx.semver.Version = "1.0.1";
|
||||
|
||||
public static final LEVEL_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x";
|
||||
public static final LEVEL_DATA_VERSION_RULE:thx.semver.VersionRule = ">=1.0.0 <1.1.0";
|
||||
|
||||
public static var instance(get, never):LevelRegistry;
|
||||
static var _instance:Null<LevelRegistry> = null;
|
||||
|
|
|
@ -3,7 +3,8 @@ package funkin.effects;
|
|||
import flixel.addons.effects.FlxTrail;
|
||||
import funkin.play.stage.Bopper;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.system.FlxAssets;
|
||||
import flixel.system.FlxAssets.FlxGraphicAsset;
|
||||
import flixel.math.FlxPoint;
|
||||
|
||||
/**
|
||||
* An offshoot of FlxTrail, but accomodates the way Funkin
|
||||
|
@ -14,27 +15,41 @@ class FunkTrail extends FlxTrail
|
|||
/**
|
||||
* Creates a new FunkTrail effect for a specific FlxSprite.
|
||||
*
|
||||
* @param Target The FlxSprite the trail is attached to.
|
||||
* @param Graphic The image to use for the trailsprites. Optional, uses the sprite's graphic if null.
|
||||
* @param Length The amount of trailsprites to create.
|
||||
* @param Delay How often to update the trail. 0 updates every frame.
|
||||
* @param Alpha The alpha value for the very first trailsprite.
|
||||
* @param Diff How much lower the alpha of the next trailsprite is.
|
||||
* @param target The FlxSprite the trail is attached to.
|
||||
* @param graphic The image to use for the trailsprites. Optional, uses the sprite's graphic if null.
|
||||
* @param length The amount of trailsprites to create.
|
||||
* @param delay How often to update the trail. 0 updates every frame.
|
||||
* @param alpha The alpha value for the very first trailsprite.
|
||||
* @param diff How much lower the alpha of the next trailsprite is.
|
||||
*/
|
||||
public function new(Target:FlxSprite, ?Graphic:FlxGraphicAsset, Length:Int = 10, Delay:Int = 3, Alpha:Float = 0.4, Diff:Float = 0.05)
|
||||
public function new(target:FlxSprite, ?graphic:FlxGraphicAsset, length:Int = 10, delay:Int = 3, alpha:Float = 0.4, diff:Float = 0.05)
|
||||
{
|
||||
super(Target, Graphic, Length, Delay, Alpha, Diff);
|
||||
super(target, graphic, length, delay, alpha, diff);
|
||||
}
|
||||
|
||||
override public function update(elapsed:Float):Void
|
||||
/**
|
||||
* An offset applied to the target position whenever a new frame is saved.
|
||||
*/
|
||||
public final frameOffset:FlxPoint = FlxPoint.get();
|
||||
|
||||
override function destroy():Void
|
||||
{
|
||||
super.destroy();
|
||||
|
||||
frameOffset.put();
|
||||
}
|
||||
|
||||
override function addTrailFrame():Void
|
||||
{
|
||||
super.addTrailFrame();
|
||||
|
||||
if (target is Bopper)
|
||||
{
|
||||
var targ:Bopper = cast target;
|
||||
@:privateAccess effectOffset.set((targ.animOffsets[0] - targ.globalOffsets[0]) * targ.scale.x,
|
||||
(targ.animOffsets[1] - targ.globalOffsets[1]) * targ.scale.y);
|
||||
}
|
||||
@:privateAccess
|
||||
frameOffset.set((targ.animOffsets[0] - targ.globalOffsets[0]) * targ.scale.x, (targ.animOffsets[1] - targ.globalOffsets[1]) * targ.scale.y);
|
||||
|
||||
super.update(elapsed);
|
||||
_recentPositions[0]?.subtract(frameOffset.x, frameOffset.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,23 +7,17 @@ import flixel.FlxSprite;
|
|||
import flixel.graphics.FlxGraphic;
|
||||
import flixel.graphics.frames.FlxFrame;
|
||||
import flixel.math.FlxMatrix;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.math.FlxRect;
|
||||
import flixel.util.FlxColor;
|
||||
import lime.graphics.cairo.Cairo;
|
||||
import openfl.display.BitmapData;
|
||||
import openfl.display.BlendMode;
|
||||
import openfl.display.DisplayObjectRenderer;
|
||||
import openfl.display.Graphics;
|
||||
import openfl.display.OpenGLRenderer;
|
||||
import openfl.display._internal.Context3DGraphics;
|
||||
import openfl.display3D.Context3D;
|
||||
import openfl.display3D.Context3DClearMask;
|
||||
import openfl.filters.BitmapFilter;
|
||||
import openfl.filters.BlurFilter;
|
||||
import openfl.geom.ColorTransform;
|
||||
import openfl.geom.Matrix;
|
||||
import openfl.geom.Point;
|
||||
import openfl.geom.Rectangle;
|
||||
#if (js && html5)
|
||||
import lime._internal.graphics.ImageCanvasUtil;
|
||||
|
|
|
@ -9,7 +9,7 @@ import funkin.graphics.framebuffer.FixedBitmapData;
|
|||
import openfl.display.BitmapData;
|
||||
import flixel.math.FlxRect;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.graphics.frames.FlxFrame;
|
||||
import flixel.graphics.frames.FlxFrame.FlxFrameAngle;
|
||||
import flixel.FlxCamera;
|
||||
|
||||
/**
|
||||
|
@ -97,14 +97,58 @@ class FunkinSprite extends FlxSprite
|
|||
return this;
|
||||
}
|
||||
|
||||
public function loadTextureAsync(key:String, fade:Bool = false):Void
|
||||
{
|
||||
var fadeTween:Null<FlxTween> = null;
|
||||
if (fade)
|
||||
{
|
||||
fadeTween = FlxTween.tween(this, {alpha: 0}, 0.25);
|
||||
}
|
||||
|
||||
trace('[ASYNC] Start loading image (${key})');
|
||||
graphic.persist = true;
|
||||
openfl.Assets.loadBitmapData(key)
|
||||
.onComplete(function(bitmapData:openfl.display.BitmapData) {
|
||||
trace('[ASYNC] Finished loading image');
|
||||
var cache:Bool = false;
|
||||
loadBitmapData(bitmapData, cache);
|
||||
|
||||
if (fadeTween != null)
|
||||
{
|
||||
fadeTween.cancel();
|
||||
FlxTween.tween(this, {alpha: 1.0}, 0.25);
|
||||
}
|
||||
})
|
||||
.onError(function(error:Dynamic) {
|
||||
trace('[ASYNC] Failed to load image: ${error}');
|
||||
if (fadeTween != null)
|
||||
{
|
||||
fadeTween.cancel();
|
||||
this.alpha = 1.0;
|
||||
}
|
||||
})
|
||||
.onProgress(function(progress:Int, total:Int) {
|
||||
trace('[ASYNC] Loading image progress: ${progress}/${total}');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply an OpenFL `BitmapData` to this sprite.
|
||||
* @param input The OpenFL `BitmapData` to apply
|
||||
* @return This sprite, for chaining
|
||||
*/
|
||||
public function loadBitmapData(input:BitmapData):FunkinSprite
|
||||
public function loadBitmapData(input:BitmapData, cache:Bool = true):FunkinSprite
|
||||
{
|
||||
loadGraphic(input);
|
||||
if (cache)
|
||||
{
|
||||
loadGraphic(input);
|
||||
}
|
||||
else
|
||||
{
|
||||
var graphic:FlxGraphic = FlxGraphic.fromBitmapData(input, false, null, false);
|
||||
this.graphic = graphic;
|
||||
this.frames = this.graphic.imageFrame;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
|
|
@ -3,10 +3,8 @@ package funkin.graphics.adobeanimate;
|
|||
import flixel.util.FlxSignal.FlxTypedSignal;
|
||||
import flxanimate.FlxAnimate;
|
||||
import flxanimate.FlxAnimate.Settings;
|
||||
import flxanimate.frames.FlxAnimateFrames;
|
||||
import flixel.graphics.frames.FlxFrame;
|
||||
import flixel.system.FlxAssets.FlxGraphicAsset;
|
||||
import openfl.display.BitmapData;
|
||||
import flixel.math.FlxPoint;
|
||||
import flxanimate.animate.FlxKeyFrame;
|
||||
|
||||
|
@ -37,6 +35,11 @@ class FlxAtlasSprite extends FlxAnimate
|
|||
*/
|
||||
public var onAnimationComplete:FlxTypedSignal<String->Void> = new FlxTypedSignal();
|
||||
|
||||
/**
|
||||
* Signal dispatched when a looping animation finishes playing.
|
||||
*/
|
||||
public var onAnimationLoop:FlxTypedSignal<String->Void> = new FlxTypedSignal();
|
||||
|
||||
var currentAnimation:String;
|
||||
|
||||
var canPlayOtherAnims:Bool = true;
|
||||
|
@ -63,8 +66,6 @@ class FlxAtlasSprite extends FlxAnimate
|
|||
throw 'FlxAtlasSprite not initialized properly. Are you sure the path (${path}) exists?';
|
||||
}
|
||||
|
||||
onAnimationComplete.add(cleanupAnimation);
|
||||
|
||||
// This defaults the sprite to play the first animation in the atlas,
|
||||
// then pauses it. This ensures symbols are intialized properly.
|
||||
this.anim.play('');
|
||||
|
@ -198,33 +199,44 @@ class FlxAtlasSprite extends FlxAnimate
|
|||
goToFrameLabel(id);
|
||||
fr = anim.getFrameLabel(id);
|
||||
anim.curFrame += startFrame;
|
||||
// Resume animation if it's paused.
|
||||
anim.resume();
|
||||
}
|
||||
}
|
||||
|
||||
override public function update(elapsed:Float)
|
||||
override public function update(elapsed:Float):Void
|
||||
{
|
||||
super.update(elapsed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the animation has finished playing.
|
||||
* Never true if animation is configured to loop.
|
||||
* @return Whether the animation has finished playing.
|
||||
*/
|
||||
public function isAnimationFinished():Bool
|
||||
{
|
||||
return this.anim.finished;
|
||||
return isLoopComplete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the animation has reached the last frame.
|
||||
* Can be true even if animation is configured to loop.
|
||||
* @return Whether the animation has reached the last frame.
|
||||
*/
|
||||
public function isLoopComplete():Bool
|
||||
{
|
||||
if (this.anim == null) return false;
|
||||
if (!this.anim.isPlaying) return false;
|
||||
|
||||
if (fr != null) return (anim.reversed && anim.curFrame < fr.index || !anim.reversed && anim.curFrame >= (fr.index + fr.duration));
|
||||
if (fr != null)
|
||||
{
|
||||
var curFrame = anim.curFrame;
|
||||
|
||||
var startFrame = fr.index;
|
||||
var endFrame = (fr.index + fr.duration);
|
||||
|
||||
return (anim.reversed) ? (curFrame < startFrame) : (curFrame >= endFrame);
|
||||
}
|
||||
|
||||
return (anim.reversed && anim.curFrame == 0 || !(anim.reversed) && (anim.curFrame) >= (anim.length - 1));
|
||||
}
|
||||
|
@ -252,7 +264,7 @@ class FlxAtlasSprite extends FlxAnimate
|
|||
this.anim.goToFrameLabel(label);
|
||||
}
|
||||
|
||||
function getFrameLabelNames(?layer:haxe.extern.EitherType<Int, String> = null)
|
||||
function getFrameLabelNames(?layer:haxe.extern.EitherType<Int, String>):Array<String>
|
||||
{
|
||||
var labels = this.anim.getFrameLabels(layer);
|
||||
var array = [];
|
||||
|
@ -295,16 +307,18 @@ class FlxAtlasSprite extends FlxAnimate
|
|||
if (isLoopComplete())
|
||||
{
|
||||
anim.pause();
|
||||
_onAnimationComplete();
|
||||
|
||||
if (looping)
|
||||
{
|
||||
anim.curFrame = (fr != null) ? fr.index : 0;
|
||||
anim.resume();
|
||||
_onAnimationLoop();
|
||||
}
|
||||
else if (fr != null && anim.curFrame != anim.length - 1)
|
||||
{
|
||||
anim.curFrame--;
|
||||
cleanupAnimation(currentAnimation ?? "");
|
||||
_onAnimationComplete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -322,11 +336,32 @@ class FlxAtlasSprite extends FlxAnimate
|
|||
}
|
||||
}
|
||||
|
||||
function _onAnimationLoop():Void
|
||||
{
|
||||
if (currentAnimation != null)
|
||||
{
|
||||
onAnimationLoop.dispatch(currentAnimation);
|
||||
}
|
||||
else
|
||||
{
|
||||
onAnimationLoop.dispatch('');
|
||||
}
|
||||
}
|
||||
|
||||
var prevFrames:Map<Int, FlxFrame> = [];
|
||||
|
||||
public function replaceFrameGraphic(index:Int, ?graphic:FlxGraphicAsset):Void
|
||||
{
|
||||
if (graphic == null || !Assets.exists(graphic))
|
||||
var cond = false;
|
||||
|
||||
if (graphic == null) cond = true;
|
||||
else
|
||||
{
|
||||
if ((graphic is String)) cond = !Assets.exists(graphic)
|
||||
else
|
||||
cond = false;
|
||||
}
|
||||
if (cond)
|
||||
{
|
||||
var prevFrame:Null<FlxFrame> = prevFrames.get(index);
|
||||
if (prevFrame == null) return;
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package funkin.graphics.shaders;
|
||||
|
||||
import flixel.system.FlxAssets.FlxShader;
|
||||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
|
||||
class BlueFade extends FlxShader
|
||||
|
|
227
source/funkin/graphics/shaders/DropShadowScreenspace.hx
Normal file
227
source/funkin/graphics/shaders/DropShadowScreenspace.hx
Normal file
|
@ -0,0 +1,227 @@
|
|||
package funkin.graphics.shaders;
|
||||
|
||||
import flixel.system.FlxAssets.FlxShader;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.math.FlxAngle;
|
||||
import flixel.graphics.frames.FlxFrame;
|
||||
import openfl.display.BitmapData;
|
||||
|
||||
/*
|
||||
A screenspace version of the DropShadowShader.. currently the only way to use this effect with
|
||||
FlxAnimate :(
|
||||
*/
|
||||
class DropShadowScreenspace extends DropShadowShader
|
||||
{
|
||||
/*
|
||||
The current zoom of the camera. Needed to figure out how much to multiply the drop shadow size.
|
||||
*/
|
||||
public var curZoom(default, set):Float;
|
||||
|
||||
function set_curZoom(val:Float):Float
|
||||
{
|
||||
curZoom = val;
|
||||
zoom.value = [val];
|
||||
return val;
|
||||
}
|
||||
|
||||
@:glFragmentSource('
|
||||
#pragma header
|
||||
|
||||
// This shader aims to mostly recreate how Adobe Animate/Flash handles drop shadows, but its main use here is for rim lighting.
|
||||
|
||||
// this shader also includes a recreation of the Animate/Flash "Adjust Color" filter,
|
||||
// which was kindly provided and written by Rozebud https://github.com/ThatRozebudDude ( thank u rozebud :) )
|
||||
// Adapted from Andrey-Postelzhuks shader found here: https://forum.unity.com/threads/hue-saturation-brightness-contrast-shader.260649/
|
||||
// Hue rotation stuff is from here: https://www.w3.org/TR/filter-effects/#feColorMatrixElement
|
||||
|
||||
// equals (frame.left, frame.top, frame.right, frame.bottom)
|
||||
uniform vec4 uFrameBounds;
|
||||
|
||||
uniform float ang;
|
||||
uniform float dist;
|
||||
uniform float str;
|
||||
uniform float thr;
|
||||
|
||||
// need to account for rotated frames... oops
|
||||
uniform float angOffset;
|
||||
|
||||
uniform sampler2D altMask;
|
||||
uniform bool useMask;
|
||||
uniform float thr2;
|
||||
|
||||
uniform vec3 dropColor;
|
||||
|
||||
uniform float hue;
|
||||
uniform float saturation;
|
||||
uniform float brightness;
|
||||
uniform float contrast;
|
||||
|
||||
uniform float zoom;
|
||||
|
||||
uniform float AA_STAGES;
|
||||
|
||||
const vec3 grayscaleValues = vec3(0.3098039215686275, 0.607843137254902, 0.0823529411764706);
|
||||
const float e = 2.718281828459045;
|
||||
|
||||
vec3 applyHueRotate(vec3 aColor, float aHue){
|
||||
float angle = radians(aHue);
|
||||
|
||||
mat3 m1 = mat3(0.213, 0.213, 0.213, 0.715, 0.715, 0.715, 0.072, 0.072, 0.072);
|
||||
mat3 m2 = mat3(0.787, -0.213, -0.213, -0.715, 0.285, -0.715, -0.072, -0.072, 0.928);
|
||||
mat3 m3 = mat3(-0.213, 0.143, -0.787, -0.715, 0.140, 0.715, 0.928, -0.283, 0.072);
|
||||
mat3 m = m1 + cos(angle) * m2 + sin(angle) * m3;
|
||||
|
||||
return m * aColor;
|
||||
}
|
||||
|
||||
vec3 applySaturation(vec3 aColor, float value){
|
||||
if(value > 0.0){ value = value * 3.0; }
|
||||
value = (1.0 + (value / 100.0));
|
||||
vec3 grayscale = vec3(dot(aColor, grayscaleValues));
|
||||
return clamp(mix(grayscale, aColor, value), 0.0, 1.0);
|
||||
}
|
||||
|
||||
vec3 applyContrast(vec3 aColor, float value){
|
||||
value = (1.0 + (value / 100.0));
|
||||
if(value > 1.0){
|
||||
value = (((0.00852259 * pow(e, 4.76454 * (value - 1.0))) * 1.01) - 0.0086078159) * 10.0; //Just roll with it...
|
||||
value += 1.0;
|
||||
}
|
||||
return clamp((aColor - 0.25) * value + 0.25, 0.0, 1.0);
|
||||
}
|
||||
|
||||
vec3 applyHSBCEffect(vec3 color){
|
||||
|
||||
//Brightness
|
||||
color = color + ((brightness) / 255.0);
|
||||
|
||||
//Hue
|
||||
color = applyHueRotate(color, hue);
|
||||
|
||||
//Contrast
|
||||
color = applyContrast(color, contrast);
|
||||
|
||||
//Saturation
|
||||
color = applySaturation(color, saturation);
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
vec2 hash22(vec2 p) {
|
||||
vec3 p3 = fract(vec3(p.xyx) * vec3(.1031, .1030, .0973));
|
||||
p3 += dot(p3, p3.yzx + 33.33);
|
||||
return fract((p3.xx + p3.yz) * p3.zy);
|
||||
}
|
||||
|
||||
float intensityPass(vec2 fragCoord, float curThreshold, bool useMask) {
|
||||
vec4 col = texture2D(bitmap, fragCoord);
|
||||
|
||||
float maskIntensity = 0.0;
|
||||
if(useMask == true){
|
||||
maskIntensity = mix(0.0, 1.0, texture2D(altMask, fragCoord).b);
|
||||
}
|
||||
|
||||
if(col.a == 0.0){
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
float intensity = dot(col.rgb, vec3(0.3098, 0.6078, 0.0823));
|
||||
|
||||
intensity = maskIntensity > 0.0 ? float(intensity > thr2) : float(intensity > thr);
|
||||
|
||||
return intensity;
|
||||
}
|
||||
|
||||
// essentially just stole this from the AngleMask shader but repurposed it to smooth
|
||||
// the threshold because without any sort of smoothing it produces horrible edges
|
||||
float antialias(vec2 fragCoord, float curThreshold, bool useMask) {
|
||||
|
||||
// In GLSL 100, we need to use constant loop bounds
|
||||
// Well assume a reasonable maximum for AA_STAGES and use a fixed loop
|
||||
// The actual number of iterations will be controlled by a condition inside
|
||||
const int MAX_AA = 8; // This should be large enough for most uses
|
||||
|
||||
float AA_TOTAL_PASSES = AA_STAGES * AA_STAGES + 1.0;
|
||||
const float AA_JITTER = 0.5;
|
||||
|
||||
// Run the shader multiple times with a random subpixel offset each time and average the results
|
||||
float color = intensityPass(fragCoord, curThreshold, useMask);
|
||||
for (int i = 0; i < MAX_AA * MAX_AA; i++) {
|
||||
// Calculate x and y from i
|
||||
int x = i / MAX_AA;
|
||||
int y = i - (MAX_AA * int(i/MAX_AA)); // poor mans modulus
|
||||
|
||||
// Skip iterations beyond our desired AA_STAGES
|
||||
if (float(x) >= AA_STAGES || float(y) >= AA_STAGES) {
|
||||
continue;
|
||||
}
|
||||
|
||||
vec2 offset = AA_JITTER * (2.0 * hash22(vec2(float(x), float(y))) - 1.0) / openfl_TextureSize.xy;
|
||||
color += intensityPass(fragCoord + offset, curThreshold, useMask);
|
||||
}
|
||||
|
||||
return color / AA_TOTAL_PASSES;
|
||||
}
|
||||
|
||||
vec3 createDropShadow(vec3 col, float curThreshold, bool useMask) {
|
||||
|
||||
// essentially a mask so that areas under the threshold dont show the rimlight (mainly the outlines)
|
||||
float intensity = antialias(openfl_TextureCoordv, curThreshold, useMask);
|
||||
|
||||
// the distance the dropshadow moves needs to be correctly scaled based on the texture size
|
||||
vec2 imageRatio = vec2(1.0/openfl_TextureSize.x, 1.0/openfl_TextureSize.y);
|
||||
|
||||
// check the pixel in the direction and distance specified
|
||||
vec2 checkedPixel = vec2(openfl_TextureCoordv.x + ((dist*zoom) * cos(ang + angOffset) * imageRatio.x), openfl_TextureCoordv.y - ((dist*zoom) * sin(ang + angOffset) * imageRatio.y));
|
||||
|
||||
// multiplier for the intensity of the drop shadow
|
||||
float dropShadowAmount = 0.0;
|
||||
|
||||
dropShadowAmount = texture2D(bitmap, checkedPixel).a;
|
||||
|
||||
// add the dropshadow color based on the amount, strength, and intensity
|
||||
col.rgb += dropColor.rgb * ((1.0 - (dropShadowAmount * str))*intensity);
|
||||
|
||||
return col;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 col = texture2D(bitmap, openfl_TextureCoordv);
|
||||
|
||||
vec3 unpremultipliedColor = col.a > 0.0 ? col.rgb / col.a : col.rgb;
|
||||
|
||||
vec3 outColor = applyHSBCEffect(unpremultipliedColor);
|
||||
|
||||
outColor = createDropShadow(outColor, thr, useMask);
|
||||
|
||||
gl_FragColor = vec4(outColor.rgb * col.a, col.a);
|
||||
}
|
||||
|
||||
')
|
||||
override public function new()
|
||||
{
|
||||
super();
|
||||
|
||||
angle = 90;
|
||||
strength = 1;
|
||||
distance = 15;
|
||||
threshold = 0.1;
|
||||
|
||||
baseHue = 0;
|
||||
baseSaturation = 0;
|
||||
baseBrightness = 0;
|
||||
baseContrast = 0;
|
||||
|
||||
useAltMask = false;
|
||||
|
||||
color = 0xFFDFEF3C;
|
||||
|
||||
antialiasAmt = 2;
|
||||
|
||||
curZoom = 1;
|
||||
|
||||
angOffset.value = [0];
|
||||
}
|
||||
}
|
451
source/funkin/graphics/shaders/DropShadowShader.hx
Normal file
451
source/funkin/graphics/shaders/DropShadowShader.hx
Normal file
|
@ -0,0 +1,451 @@
|
|||
package funkin.graphics.shaders;
|
||||
|
||||
import flixel.system.FlxAssets.FlxShader;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.math.FlxAngle;
|
||||
import flixel.graphics.frames.FlxFrame;
|
||||
import openfl.display.BitmapData;
|
||||
|
||||
/*
|
||||
A shader that aims to *mostly recreate how Adobe Animate/Flash handles drop shadows, but its main use here is for rim lighting.
|
||||
|
||||
Has options for color, angle, distance, and a threshold to not cast the shadow on parts like outlines.
|
||||
Can also be supplied a secondary mask which can then have an alternate threshold, for when sprites have too many conflicting colors
|
||||
for the drop shadow to look right (e.g. the tankmen on GF's speakers).
|
||||
|
||||
Also has an Adjust Color shader in here so they can work together when needed.
|
||||
*/
|
||||
class DropShadowShader extends FlxShader
|
||||
{
|
||||
/*
|
||||
The color of the drop shadow.
|
||||
*/
|
||||
public var color(default, set):FlxColor;
|
||||
|
||||
/*
|
||||
The angle of the drop shadow.
|
||||
|
||||
for reference, depending on the angle, the affected side will be:
|
||||
0 = RIGHT
|
||||
90 = UP
|
||||
180 = LEFT
|
||||
270 = DOWN
|
||||
*/
|
||||
public var angle(default, set):Float;
|
||||
|
||||
/*
|
||||
The distance or size of the drop shadow, in pixels,
|
||||
relative to the texture itself... NOT the camera.
|
||||
*/
|
||||
public var distance(default, set):Float;
|
||||
|
||||
/*
|
||||
The strength of the drop shadow.
|
||||
Effectively just an alpha multiplier.
|
||||
*/
|
||||
public var strength(default, set):Float;
|
||||
|
||||
/*
|
||||
The brightness threshold for the drop shadow.
|
||||
Anything below this number will NOT be affected by the drop shadow shader.
|
||||
A value of 0 effectively means theres no threshold, and vice versa.
|
||||
*/
|
||||
public var threshold(default, set):Float;
|
||||
|
||||
/*
|
||||
The amount of antialias samples per-pixel,
|
||||
used to smooth out any hard edges the brightness thresholding creates.
|
||||
Defaults to 2, and 0 will remove any smoothing.
|
||||
*/
|
||||
public var antialiasAmt(default, set):Float;
|
||||
|
||||
/*
|
||||
Whether the shader should try and use the alternate mask.
|
||||
False by default.
|
||||
*/
|
||||
public var useAltMask(default, set):Bool;
|
||||
|
||||
/*
|
||||
The image for the alternate mask.
|
||||
At the moment, it uses the blue channel to specify what is or isnt going to use the alternate threshold.
|
||||
(its kinda sloppy rn i need to make it work a little nicer)
|
||||
TODO: maybe have a sort of "threshold intensity texture" as well? where higher/lower values indicate threshold strength..
|
||||
*/
|
||||
public var altMaskImage(default, set):BitmapData;
|
||||
|
||||
/*
|
||||
An alternate brightness threshold for the drop shadow.
|
||||
Anything below this number will NOT be affected by the drop shadow shader,
|
||||
but ONLY when the pixel is within the mask.
|
||||
*/
|
||||
public var maskThreshold(default, set):Float;
|
||||
|
||||
/*
|
||||
The FlxSprite that the shader should get the frame data from.
|
||||
Needed to keep the drop shadow shader in the correct bounds and rotation.
|
||||
*/
|
||||
public var attachedSprite(default, set):FlxSprite;
|
||||
|
||||
/*
|
||||
The hue component of the Adjust Color part of the shader.
|
||||
*/
|
||||
public var baseHue(default, set):Float;
|
||||
|
||||
/*
|
||||
The saturation component of the Adjust Color part of the shader.
|
||||
*/
|
||||
public var baseSaturation(default, set):Float;
|
||||
|
||||
/*
|
||||
The brightness component of the Adjust Color part of the shader.
|
||||
*/
|
||||
public var baseBrightness(default, set):Float;
|
||||
|
||||
/*
|
||||
The contrast component of the Adjust Color part of the shader.
|
||||
*/
|
||||
public var baseContrast(default, set):Float;
|
||||
|
||||
/*
|
||||
Sets all 4 adjust color values.
|
||||
*/
|
||||
public function setAdjustColor(b:Float, h:Float, c:Float, s:Float)
|
||||
{
|
||||
baseBrightness = b;
|
||||
baseHue = h;
|
||||
baseContrast = c;
|
||||
baseSaturation = s;
|
||||
}
|
||||
|
||||
function set_baseHue(val:Float):Float
|
||||
{
|
||||
baseHue = val;
|
||||
hue.value = [val];
|
||||
return val;
|
||||
}
|
||||
|
||||
function set_baseSaturation(val:Float):Float
|
||||
{
|
||||
baseSaturation = val;
|
||||
saturation.value = [val];
|
||||
return val;
|
||||
}
|
||||
|
||||
function set_baseBrightness(val:Float):Float
|
||||
{
|
||||
baseBrightness = val;
|
||||
brightness.value = [val];
|
||||
return val;
|
||||
}
|
||||
|
||||
function set_baseContrast(val:Float):Float
|
||||
{
|
||||
baseContrast = val;
|
||||
contrast.value = [val];
|
||||
return val;
|
||||
}
|
||||
|
||||
function set_threshold(val:Float):Float
|
||||
{
|
||||
threshold = val;
|
||||
thr.value = [val];
|
||||
return val;
|
||||
}
|
||||
|
||||
function set_antialiasAmt(val:Float):Float
|
||||
{
|
||||
antialiasAmt = val;
|
||||
AA_STAGES.value = [val];
|
||||
return val;
|
||||
}
|
||||
|
||||
function set_color(col:FlxColor):FlxColor
|
||||
{
|
||||
color = col;
|
||||
dropColor.value = [color.red / 255, color.green / 255, color.blue / 255];
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
function set_angle(val:Float):Float
|
||||
{
|
||||
angle = val;
|
||||
ang.value = [angle * FlxAngle.TO_RAD];
|
||||
return angle;
|
||||
}
|
||||
|
||||
function set_distance(val:Float):Float
|
||||
{
|
||||
distance = val;
|
||||
dist.value = [val];
|
||||
return val;
|
||||
}
|
||||
|
||||
function set_strength(val:Float):Float
|
||||
{
|
||||
strength = val;
|
||||
str.value = [val];
|
||||
return val;
|
||||
}
|
||||
|
||||
function set_attachedSprite(spr:FlxSprite):FlxSprite
|
||||
{
|
||||
attachedSprite = spr;
|
||||
updateFrameInfo(attachedSprite.frame);
|
||||
return spr;
|
||||
}
|
||||
|
||||
/*
|
||||
Loads an image for the mask.
|
||||
While you *could* directly set the value of the mask, this function works for both HTML5 and desktop targets.
|
||||
*/
|
||||
public function loadAltMask(path:String)
|
||||
{
|
||||
#if html5
|
||||
BitmapData.loadFromFile(path).onComplete(function(bmp:BitmapData) {
|
||||
altMaskImage = bmp;
|
||||
});
|
||||
#else
|
||||
altMaskImage = BitmapData.fromFile(path);
|
||||
#end
|
||||
}
|
||||
|
||||
/*
|
||||
Should be called on the animation.callback of the attached sprite.
|
||||
TODO: figure out why the reference to the attachedSprite breaks on web??
|
||||
*/
|
||||
public function onAttachedFrame(name, frameNum, frameIndex)
|
||||
{
|
||||
if (attachedSprite != null) updateFrameInfo(attachedSprite.frame);
|
||||
}
|
||||
|
||||
/*
|
||||
Updates the frame bounds and angle offset of the sprite for the shader.
|
||||
*/
|
||||
public function updateFrameInfo(frame:FlxFrame)
|
||||
{
|
||||
// NOTE: uv.width is actually the right pos and uv.height is the bottom pos
|
||||
uFrameBounds.value = [frame.uv.x, frame.uv.y, frame.uv.width, frame.uv.height];
|
||||
|
||||
// if a frame is rotated the shader will look completely wrong lol
|
||||
angOffset.value = [frame.angle * FlxAngle.TO_RAD];
|
||||
}
|
||||
|
||||
function set_altMaskImage(_bitmapData:BitmapData):BitmapData
|
||||
{
|
||||
altMask.input = _bitmapData;
|
||||
|
||||
return _bitmapData;
|
||||
}
|
||||
|
||||
function set_maskThreshold(val:Float):Float
|
||||
{
|
||||
maskThreshold = val;
|
||||
thr2.value = [val];
|
||||
return val;
|
||||
}
|
||||
|
||||
function set_useAltMask(val:Bool):Bool
|
||||
{
|
||||
useAltMask = val;
|
||||
useMask.value = [val];
|
||||
return val;
|
||||
}
|
||||
|
||||
@:glFragmentSource('
|
||||
#pragma header
|
||||
|
||||
// This shader aims to mostly recreate how Adobe Animate/Flash handles drop shadows, but its main use here is for rim lighting.
|
||||
|
||||
// this shader also includes a recreation of the Animate/Flash "Adjust Color" filter,
|
||||
// which was kindly provided and written by Rozebud https://github.com/ThatRozebudDude ( thank u rozebud :) )
|
||||
// Adapted from Andrey-Postelzhuks shader found here: https://forum.unity.com/threads/hue-saturation-brightness-contrast-shader.260649/
|
||||
// Hue rotation stuff is from here: https://www.w3.org/TR/filter-effects/#feColorMatrixElement
|
||||
|
||||
// equals (frame.left, frame.top, frame.right, frame.bottom)
|
||||
uniform vec4 uFrameBounds;
|
||||
|
||||
uniform float ang;
|
||||
uniform float dist;
|
||||
uniform float str;
|
||||
uniform float thr;
|
||||
|
||||
// need to account for rotated frames... oops
|
||||
uniform float angOffset;
|
||||
|
||||
uniform sampler2D altMask;
|
||||
uniform bool useMask;
|
||||
uniform float thr2;
|
||||
|
||||
uniform vec3 dropColor;
|
||||
|
||||
uniform float hue;
|
||||
uniform float saturation;
|
||||
uniform float brightness;
|
||||
uniform float contrast;
|
||||
|
||||
uniform float AA_STAGES;
|
||||
|
||||
const vec3 grayscaleValues = vec3(0.3098039215686275, 0.607843137254902, 0.0823529411764706);
|
||||
const float e = 2.718281828459045;
|
||||
|
||||
vec3 applyHueRotate(vec3 aColor, float aHue){
|
||||
float angle = radians(aHue);
|
||||
|
||||
mat3 m1 = mat3(0.213, 0.213, 0.213, 0.715, 0.715, 0.715, 0.072, 0.072, 0.072);
|
||||
mat3 m2 = mat3(0.787, -0.213, -0.213, -0.715, 0.285, -0.715, -0.072, -0.072, 0.928);
|
||||
mat3 m3 = mat3(-0.213, 0.143, -0.787, -0.715, 0.140, 0.715, 0.928, -0.283, 0.072);
|
||||
mat3 m = m1 + cos(angle) * m2 + sin(angle) * m3;
|
||||
|
||||
return m * aColor;
|
||||
}
|
||||
|
||||
vec3 applySaturation(vec3 aColor, float value){
|
||||
if(value > 0.0){ value = value * 3.0; }
|
||||
value = (1.0 + (value / 100.0));
|
||||
vec3 grayscale = vec3(dot(aColor, grayscaleValues));
|
||||
return clamp(mix(grayscale, aColor, value), 0.0, 1.0);
|
||||
}
|
||||
|
||||
vec3 applyContrast(vec3 aColor, float value){
|
||||
value = (1.0 + (value / 100.0));
|
||||
if(value > 1.0){
|
||||
value = (((0.00852259 * pow(e, 4.76454 * (value - 1.0))) * 1.01) - 0.0086078159) * 10.0; //Just roll with it...
|
||||
value += 1.0;
|
||||
}
|
||||
return clamp((aColor - 0.25) * value + 0.25, 0.0, 1.0);
|
||||
}
|
||||
|
||||
vec3 applyHSBCEffect(vec3 color){
|
||||
|
||||
//Brightness
|
||||
color = color + ((brightness) / 255.0);
|
||||
|
||||
//Hue
|
||||
color = applyHueRotate(color, hue);
|
||||
|
||||
//Contrast
|
||||
color = applyContrast(color, contrast);
|
||||
|
||||
//Saturation
|
||||
color = applySaturation(color, saturation);
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
vec2 hash22(vec2 p) {
|
||||
vec3 p3 = fract(vec3(p.xyx) * vec3(.1031, .1030, .0973));
|
||||
p3 += dot(p3, p3.yzx + 33.33);
|
||||
return fract((p3.xx + p3.yz) * p3.zy);
|
||||
}
|
||||
|
||||
float intensityPass(vec2 fragCoord, float curThreshold, bool useMask) {
|
||||
vec4 col = texture2D(bitmap, fragCoord);
|
||||
|
||||
float maskIntensity = 0.0;
|
||||
if(useMask == true){
|
||||
maskIntensity = mix(0.0, 1.0, texture2D(altMask, fragCoord).b);
|
||||
}
|
||||
|
||||
if(col.a == 0.0){
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
float intensity = dot(col.rgb, vec3(0.3098, 0.6078, 0.0823));
|
||||
|
||||
intensity = maskIntensity > 0.0 ? float(intensity > thr2) : float(intensity > thr);
|
||||
|
||||
return intensity;
|
||||
}
|
||||
|
||||
// essentially just stole this from the AngleMask shader but repurposed it to smooth
|
||||
// the threshold because without any sort of smoothing it produces horrible edges
|
||||
float antialias(vec2 fragCoord, float curThreshold, bool useMask) {
|
||||
|
||||
// In GLSL 100, we need to use constant loop bounds
|
||||
// Well assume a reasonable maximum for AA_STAGES and use a fixed loop
|
||||
// The actual number of iterations will be controlled by a condition inside
|
||||
const int MAX_AA = 8; // This should be large enough for most uses
|
||||
|
||||
float AA_TOTAL_PASSES = AA_STAGES * AA_STAGES + 1.0;
|
||||
const float AA_JITTER = 0.5;
|
||||
|
||||
// Run the shader multiple times with a random subpixel offset each time and average the results
|
||||
float color = intensityPass(fragCoord, curThreshold, useMask);
|
||||
for (int i = 0; i < MAX_AA * MAX_AA; i++) {
|
||||
// Calculate x and y from i
|
||||
int x = i / MAX_AA;
|
||||
int y = i - (MAX_AA * int(i/MAX_AA)); // poor mans modulus
|
||||
|
||||
// Skip iterations beyond our desired AA_STAGES
|
||||
if (float(x) >= AA_STAGES || float(y) >= AA_STAGES) {
|
||||
continue;
|
||||
}
|
||||
|
||||
vec2 offset = AA_JITTER * (2.0 * hash22(vec2(float(x), float(y))) - 1.0) / openfl_TextureSize.xy;
|
||||
color += intensityPass(fragCoord + offset, curThreshold, useMask);
|
||||
}
|
||||
|
||||
return color / AA_TOTAL_PASSES;
|
||||
}
|
||||
|
||||
vec3 createDropShadow(vec3 col, float curThreshold, bool useMask) {
|
||||
|
||||
// essentially a mask so that areas under the threshold dont show the rimlight (mainly the outlines)
|
||||
float intensity = antialias(openfl_TextureCoordv, curThreshold, useMask);
|
||||
|
||||
// the distance the dropshadow moves needs to be correctly scaled based on the texture size
|
||||
vec2 imageRatio = vec2(1.0/openfl_TextureSize.x, 1.0/openfl_TextureSize.y);
|
||||
|
||||
// check the pixel in the direction and distance specified
|
||||
vec2 checkedPixel = vec2(openfl_TextureCoordv.x + (dist * cos(ang + angOffset) * imageRatio.x), openfl_TextureCoordv.y - (dist * sin(ang + angOffset) * imageRatio.y));
|
||||
|
||||
// multiplier for the intensity of the drop shadow
|
||||
float dropShadowAmount = 0.0;
|
||||
|
||||
if(checkedPixel.x > uFrameBounds.x && checkedPixel.y > uFrameBounds.y && checkedPixel.x < uFrameBounds.z && checkedPixel.y < uFrameBounds.w){
|
||||
dropShadowAmount = texture2D(bitmap, checkedPixel).a;
|
||||
}
|
||||
|
||||
// add the dropshadow color based on the amount, strength, and intensity
|
||||
col.rgb += dropColor.rgb * ((1.0 - (dropShadowAmount * str))*intensity);
|
||||
|
||||
return col;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 col = texture2D(bitmap, openfl_TextureCoordv);
|
||||
|
||||
vec3 unpremultipliedColor = col.a > 0.0 ? col.rgb / col.a : col.rgb;
|
||||
|
||||
vec3 outColor = applyHSBCEffect(unpremultipliedColor);
|
||||
|
||||
outColor = createDropShadow(outColor, thr, useMask);
|
||||
|
||||
gl_FragColor = vec4(outColor.rgb * col.a, col.a);
|
||||
}
|
||||
|
||||
')
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
|
||||
angle = 0;
|
||||
strength = 1;
|
||||
distance = 15;
|
||||
threshold = 0.1;
|
||||
|
||||
baseHue = 0;
|
||||
baseSaturation = 0;
|
||||
baseBrightness = 0;
|
||||
baseContrast = 0;
|
||||
|
||||
antialiasAmt = 2;
|
||||
|
||||
useAltMask = false;
|
||||
|
||||
angOffset.value = [0];
|
||||
}
|
||||
}
|
|
@ -8,13 +8,13 @@ class HSVShader extends FlxRuntimeShader
|
|||
public var saturation(default, set):Float;
|
||||
public var value(default, set):Float;
|
||||
|
||||
public function new()
|
||||
public function new(h:Float = 1, s:Float = 1, v:Float = 1)
|
||||
{
|
||||
super(Assets.getText(Paths.frag('hsv')));
|
||||
FlxG.debugger.addTrackerProfile(new TrackerProfile(HSVShader, ['hue', 'saturation', 'value']));
|
||||
hue = 1;
|
||||
saturation = 1;
|
||||
value = 1;
|
||||
hue = h;
|
||||
saturation = s;
|
||||
value = v;
|
||||
}
|
||||
|
||||
function set_hue(value:Float):Float
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package funkin.graphics.shaders;
|
||||
|
||||
import flixel.system.FlxAssets.FlxShader;
|
||||
import openfl.display.BitmapData;
|
||||
import openfl.display.ShaderParameter;
|
||||
import openfl.display.ShaderParameterType;
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package funkin.graphics.shaders;
|
||||
|
||||
import flixel.system.FlxAssets.FlxShader;
|
||||
import flixel.util.FlxColor;
|
||||
import openfl.display.BitmapData;
|
||||
|
||||
class TextureSwap extends FlxShader
|
||||
|
@ -9,6 +8,17 @@ class TextureSwap extends FlxShader
|
|||
public var swappedImage(default, set):BitmapData;
|
||||
public var amount(default, set):Float;
|
||||
|
||||
public function loadSwapImage(path:String)
|
||||
{
|
||||
#if html5
|
||||
BitmapData.loadFromFile(path).onComplete(function(bmp:BitmapData) {
|
||||
swappedImage = bmp;
|
||||
});
|
||||
#else
|
||||
swappedImage = BitmapData.fromFile(path);
|
||||
#end
|
||||
}
|
||||
|
||||
function set_swappedImage(_bitmapData:BitmapData):BitmapData
|
||||
{
|
||||
image.input = _bitmapData;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package funkin.graphics.shaders;
|
||||
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.system.FlxAssets.FlxShader;
|
||||
|
||||
class TitleOutline extends FlxShader
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
package funkin.graphics.video;
|
||||
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxSignal.FlxTypedSignal;
|
||||
import funkin.audio.FunkinSound;
|
||||
import openfl.display3D.textures.TextureBase;
|
||||
import openfl.events.NetStatusEvent;
|
||||
import openfl.media.SoundTransform;
|
||||
import openfl.media.Video;
|
||||
|
@ -12,8 +10,8 @@ import openfl.net.NetStream;
|
|||
|
||||
/**
|
||||
* Plays a video via a NetStream. Only works on HTML5.
|
||||
* This does NOT replace hxCodec, nor does hxCodec replace this.
|
||||
* hxCodec only works on desktop and does not work on HTML5!
|
||||
* This does NOT replace hxvlc, nor does hxvlc replace this.
|
||||
* hxvlc only works on desktop and does not work on HTML5!
|
||||
*/
|
||||
class FlxVideo extends FunkinSprite
|
||||
{
|
||||
|
|
|
@ -1,34 +1,17 @@
|
|||
package funkin.graphics.video;
|
||||
|
||||
#if hxCodec
|
||||
import hxcodec.flixel.FlxVideoSprite;
|
||||
#if hxvlc
|
||||
import hxvlc.flixel.FlxVideoSprite;
|
||||
|
||||
/**
|
||||
* Not to be confused with FlxVideo, this is a hxcodec based video class
|
||||
* Not to be confused with FlxVideo, this is a hxvlc based video class
|
||||
* We override it simply to correct/control our volume easier.
|
||||
*/
|
||||
class FunkinVideoSprite extends FlxVideoSprite
|
||||
{
|
||||
public var volume(default, set):Float = 1;
|
||||
|
||||
public function new(x:Float = 0, y:Float = 0)
|
||||
{
|
||||
super(x, y);
|
||||
|
||||
set_volume(1);
|
||||
}
|
||||
|
||||
override public function update(elapsed:Float):Void
|
||||
{
|
||||
super.update(elapsed);
|
||||
set_volume(volume);
|
||||
}
|
||||
|
||||
function set_volume(value:Float):Float
|
||||
{
|
||||
volume = value;
|
||||
bitmap.volume = Std.int((FlxG.sound.muted ? 0 : 1) * (FlxG.sound.logToLinear(FlxG.sound.volume) * 100) * volume);
|
||||
return volume;
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
|
|
@ -2,23 +2,17 @@ package funkin.input;
|
|||
|
||||
import flixel.input.gamepad.FlxGamepad;
|
||||
import flixel.util.FlxDirectionFlags;
|
||||
import flixel.FlxObject;
|
||||
import flixel.input.FlxInput;
|
||||
import flixel.input.FlxInput.FlxInputState;
|
||||
import flixel.input.actions.FlxAction;
|
||||
import flixel.input.actions.FlxActionInput;
|
||||
import flixel.input.actions.FlxActionInputAnalog.FlxActionInputAnalogClickAndDragMouseMotion;
|
||||
import flixel.input.actions.FlxActionInputDigital;
|
||||
import flixel.input.actions.FlxActionManager;
|
||||
import flixel.input.actions.FlxActionSet;
|
||||
import flixel.input.android.FlxAndroidKey;
|
||||
import flixel.input.gamepad.FlxGamepadButton;
|
||||
import flixel.input.gamepad.FlxGamepadInputID;
|
||||
import flixel.input.keyboard.FlxKey;
|
||||
import flixel.input.mouse.FlxMouseButton.FlxMouseButtonID;
|
||||
import flixel.math.FlxAngle;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxTimer;
|
||||
import lime.ui.Haptic;
|
||||
|
||||
/**
|
||||
|
@ -423,6 +417,7 @@ class Controls extends FlxActionSet
|
|||
|
||||
public function getDialogueName(action:FlxActionDigital, ?ignoreSurrounding:Bool = false):String
|
||||
{
|
||||
if (action.inputs.length == 0) return 'N/A';
|
||||
var input = action.inputs[0];
|
||||
if (ignoreSurrounding == false)
|
||||
{
|
||||
|
@ -731,7 +726,7 @@ class Controls extends FlxActionSet
|
|||
forEachBound(control, function(action, state) addKeys(action, keys, state));
|
||||
}
|
||||
|
||||
public function bindSwipe(control:Control, swipeDir:Int = FlxDirectionFlags.UP, ?swpLength:Float = 90)
|
||||
public function bindSwipe(control:Control, swipeDir:FlxDirectionFlags = FlxDirectionFlags.UP, ?swpLength:Float = 90)
|
||||
{
|
||||
forEachBound(control, function(action, press) action.add(new FlxActionInputDigitalMobileSwipeGameplay(swipeDir, press, swpLength)));
|
||||
}
|
||||
|
@ -1483,9 +1478,9 @@ class FlxActionInputDigitalMobileSwipeGameplay extends FlxActionInputDigital
|
|||
var activateLength:Float = 90;
|
||||
var hapticPressure:Int = 100;
|
||||
|
||||
public function new(swipeDir:Int = FlxDirectionFlags.ANY, Trigger:FlxInputState, ?swipeLength:Float = 90)
|
||||
public function new(swipeDir:FlxDirectionFlags = FlxDirectionFlags.ANY, Trigger:FlxInputState, ?swipeLength:Float = 90)
|
||||
{
|
||||
super(OTHER, swipeDir, Trigger);
|
||||
super(OTHER, swipeDir.toInt(), Trigger);
|
||||
|
||||
activateLength = swipeLength;
|
||||
}
|
||||
|
@ -1567,16 +1562,21 @@ class FlxActionInputDigitalMobileSwipeGameplay extends FlxActionInputDigital
|
|||
case JUST_PRESSED:
|
||||
if (swp.touchLength >= activateLength)
|
||||
{
|
||||
switch (inputID)
|
||||
if (inputID == FlxDirectionFlags.UP.toInt())
|
||||
{
|
||||
case FlxDirectionFlags.UP:
|
||||
if (degAngle >= 45 && degAngle <= 90 + 45) return properTouch(swp);
|
||||
case FlxDirectionFlags.DOWN:
|
||||
if (-degAngle >= 45 && -degAngle <= 90 + 45) return properTouch(swp);
|
||||
case FlxDirectionFlags.LEFT:
|
||||
if (degAngle <= 45 && -degAngle <= 45) return properTouch(swp);
|
||||
case FlxDirectionFlags.RIGHT:
|
||||
if (degAngle >= 90 + 45 && degAngle <= -90 + -45) return properTouch(swp);
|
||||
if (degAngle >= 45 && degAngle <= 90 + 45) return properTouch(swp);
|
||||
}
|
||||
else if (inputID == FlxDirectionFlags.DOWN.toInt())
|
||||
{
|
||||
if (-degAngle >= 45 && -degAngle <= 90 + 45) return properTouch(swp);
|
||||
}
|
||||
else if (inputID == FlxDirectionFlags.LEFT.toInt())
|
||||
{
|
||||
if (degAngle <= 45 && -degAngle <= 45) return properTouch(swp);
|
||||
}
|
||||
else if (inputID == FlxDirectionFlags.RIGHT.toInt())
|
||||
{
|
||||
if (degAngle >= 90 + 45 && degAngle <= -90 + -45) return properTouch(swp);
|
||||
}
|
||||
}
|
||||
default:
|
||||
|
|
|
@ -151,7 +151,7 @@ class PolymodHandler
|
|||
frameworkParams: buildFrameworkParams(),
|
||||
|
||||
// List of filenames to ignore in mods. Use the default list to ignore the metadata file, etc.
|
||||
ignoredFiles: Polymod.getDefaultIgnoreList(),
|
||||
ignoredFiles: buildIgnoreList(),
|
||||
|
||||
// Parsing rules for various data formats.
|
||||
parseRules: buildParseRules(),
|
||||
|
@ -257,12 +257,12 @@ class PolymodHandler
|
|||
Polymod.blacklistImport('Sys');
|
||||
|
||||
// `Reflect`
|
||||
// Reflect.callMethod() can access blacklisted packages
|
||||
Polymod.blacklistImport('Reflect');
|
||||
// Reflect.callMethod() can access blacklisted packages, but some functions are whitelisted
|
||||
Polymod.addImportAlias('Reflect', funkin.util.ReflectUtil);
|
||||
|
||||
// `Type`
|
||||
// Type.createInstance(Type.resolveClass()) can access blacklisted packages
|
||||
Polymod.blacklistImport('Type');
|
||||
// Type.createInstance(Type.resolveClass()) can access blacklisted packages, but some functions are whitelisted
|
||||
Polymod.addImportAlias('Type', funkin.util.ReflectUtil);
|
||||
|
||||
// `cpp.Lib`
|
||||
// Lib.load() can load malicious DLLs
|
||||
|
@ -296,6 +296,15 @@ class PolymodHandler
|
|||
// Can load native processes on the host operating system.
|
||||
Polymod.blacklistImport('openfl.desktop.NativeProcess');
|
||||
|
||||
// `funkin.api.*`
|
||||
// Contains functions which may allow for cheating and such.
|
||||
for (cls in ClassMacro.listClassesInPackage('funkin.api'))
|
||||
{
|
||||
if (cls == null) continue;
|
||||
var className:String = Type.getClassName(cls);
|
||||
Polymod.blacklistImport(className);
|
||||
}
|
||||
|
||||
// `polymod.*`
|
||||
// Contains functions which may allow for un-blacklisting other modules.
|
||||
for (cls in ClassMacro.listClassesInPackage('polymod'))
|
||||
|
@ -305,6 +314,24 @@ class PolymodHandler
|
|||
Polymod.blacklistImport(className);
|
||||
}
|
||||
|
||||
// `funkin.api.newgrounds.*`
|
||||
// Contains functions which allow for cheating medals and leaderboards.
|
||||
for (cls in ClassMacro.listClassesInPackage('funkin.api.newgrounds'))
|
||||
{
|
||||
if (cls == null) continue;
|
||||
var className:String = Type.getClassName(cls);
|
||||
Polymod.blacklistImport(className);
|
||||
}
|
||||
|
||||
// `io.newgrounds.*`
|
||||
// Contains functions which allow for cheating medals and leaderboards.
|
||||
for (cls in ClassMacro.listClassesInPackage('io.newgrounds'))
|
||||
{
|
||||
if (cls == null) continue;
|
||||
var className:String = Type.getClassName(cls);
|
||||
Polymod.blacklistImport(className);
|
||||
}
|
||||
|
||||
// `sys.*`
|
||||
// Access to system utilities such as the file system.
|
||||
for (cls in ClassMacro.listClassesInPackage('sys'))
|
||||
|
@ -315,6 +342,21 @@ class PolymodHandler
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a list of file paths that will be ignored in mods.
|
||||
*/
|
||||
static function buildIgnoreList():Array<String>
|
||||
{
|
||||
var result = Polymod.getDefaultIgnoreList();
|
||||
|
||||
result.push('.git');
|
||||
result.push('.gitignore');
|
||||
result.push('.gitattributes');
|
||||
result.push('README.md');
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static function buildParseRules():polymod.format.ParseRules
|
||||
{
|
||||
var output:polymod.format.ParseRules = polymod.format.ParseRules.getDefault();
|
||||
|
|
|
@ -8,7 +8,6 @@ import funkin.play.notes.NoteSprite;
|
|||
import funkin.play.cutscene.dialogue.Conversation;
|
||||
import funkin.play.Countdown.CountdownStep;
|
||||
import funkin.play.notes.NoteDirection;
|
||||
import openfl.events.EventType;
|
||||
import openfl.events.KeyboardEvent;
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,10 +2,6 @@ package funkin.play;
|
|||
|
||||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.FlxSprite;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.modding.events.ScriptEventDispatcher;
|
||||
import funkin.modding.module.ModuleHandler;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.modding.events.ScriptEvent.CountdownScriptEvent;
|
||||
import flixel.util.FlxTimer;
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package funkin.play;
|
||||
|
||||
import flixel.FlxState;
|
||||
import funkin.data.freeplay.player.PlayerRegistry;
|
||||
import flixel.FlxG;
|
||||
import flixel.FlxObject;
|
||||
import flixel.FlxSprite;
|
||||
|
@ -145,10 +147,13 @@ class GameOverSubState extends MusicBeatSubState
|
|||
else
|
||||
{
|
||||
boyfriend = PlayState.instance.currentStage.getBoyfriend(true);
|
||||
boyfriend.canPlayOtherAnims = true;
|
||||
boyfriend.isDead = true;
|
||||
add(boyfriend);
|
||||
boyfriend.resetCharacter();
|
||||
if (boyfriend != null)
|
||||
{
|
||||
boyfriend.canPlayOtherAnims = true;
|
||||
boyfriend.isDead = true;
|
||||
add(boyfriend);
|
||||
boyfriend.resetCharacter();
|
||||
}
|
||||
}
|
||||
|
||||
setCameraTarget();
|
||||
|
@ -269,6 +274,14 @@ class GameOverSubState extends MusicBeatSubState
|
|||
// PlayState.seenCutscene = false; // old thing...
|
||||
if (gameOverMusic != null) gameOverMusic.stop();
|
||||
|
||||
// Stop death quotes immediately.
|
||||
hasPlayedDeathQuote = true;
|
||||
if (deathQuoteSound != null)
|
||||
{
|
||||
deathQuoteSound.stop();
|
||||
deathQuoteSound = null;
|
||||
}
|
||||
|
||||
if (isChartingMode)
|
||||
{
|
||||
this.close();
|
||||
|
@ -276,13 +289,26 @@ class GameOverSubState extends MusicBeatSubState
|
|||
PlayState.instance.close(); // This only works because PlayState is a substate!
|
||||
return;
|
||||
}
|
||||
else if (PlayStatePlaylist.isStoryMode)
|
||||
{
|
||||
openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new StoryMenuState(sticker)));
|
||||
}
|
||||
else
|
||||
{
|
||||
openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> FreeplayState.build(sticker)));
|
||||
var targetState:funkin.ui.transition.StickerSubState->FlxState = (PlayStatePlaylist.isStoryMode) ? (sticker) ->
|
||||
new StoryMenuState(sticker) : (sticker) -> FreeplayState.build(sticker);
|
||||
|
||||
if (PlayStatePlaylist.isStoryMode)
|
||||
{
|
||||
PlayStatePlaylist.reset();
|
||||
}
|
||||
|
||||
var playerCharacterId = PlayerRegistry.instance.getCharacterOwnerId(PlayState.instance.currentChart.characters.player);
|
||||
var stickerSet = (playerCharacterId == "pico") ? "stickers-set-2" : "stickers-set-1";
|
||||
var stickerPack = switch (PlayState.instance.currentChart.song.id)
|
||||
{
|
||||
case "tutorial": "tutorial";
|
||||
case "darnell" | "lit-up" | "2hot": "weekend";
|
||||
default: "all";
|
||||
};
|
||||
|
||||
openSubState(new funkin.ui.transition.StickerSubState({targetState: targetState, stickerSet: stickerSet, stickerPack: stickerPack}));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -301,26 +327,23 @@ class GameOverSubState extends MusicBeatSubState
|
|||
else
|
||||
{
|
||||
// Music hasn't started yet.
|
||||
switch (PlayStatePlaylist.campaignId)
|
||||
|
||||
if (boyfriend.getDeathQuote() != null)
|
||||
{
|
||||
// TODO: Make the behavior for playing Jeff's voicelines generic or un-hardcoded.
|
||||
// This will simplify the class and make it easier for mods to add death quotes.
|
||||
case 'week7':
|
||||
if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && boyfriend.isAnimationFinished() && !playingJeffQuote)
|
||||
{
|
||||
playingJeffQuote = true;
|
||||
playJeffQuote();
|
||||
// Start music at lower volume
|
||||
startDeathMusic(0.2, false);
|
||||
boyfriend.playAnimation('deathLoop' + animationSuffix);
|
||||
}
|
||||
default:
|
||||
// Start music at normal volume once the initial death animation finishes.
|
||||
if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && boyfriend.isAnimationFinished())
|
||||
{
|
||||
startDeathMusic(1.0, false);
|
||||
boyfriend.playAnimation('deathLoop' + animationSuffix);
|
||||
}
|
||||
if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && boyfriend.isAnimationFinished() && !hasPlayedDeathQuote)
|
||||
{
|
||||
hasPlayedDeathQuote = true;
|
||||
playDeathQuote();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Start music at normal volume once the initial death animation finishes.
|
||||
if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && boyfriend.isAnimationFinished())
|
||||
{
|
||||
startDeathMusic(1.0, false);
|
||||
boyfriend.playAnimation('deathLoop' + animationSuffix);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -329,6 +352,33 @@ class GameOverSubState extends MusicBeatSubState
|
|||
super.update(elapsed);
|
||||
}
|
||||
|
||||
var deathQuoteSound:Null<FunkinSound> = null;
|
||||
|
||||
function playDeathQuote():Void
|
||||
{
|
||||
if (boyfriend == null) return;
|
||||
|
||||
var deathQuote = boyfriend.getDeathQuote();
|
||||
if (deathQuote == null) return;
|
||||
|
||||
if (deathQuoteSound != null)
|
||||
{
|
||||
deathQuoteSound.stop();
|
||||
deathQuoteSound = null;
|
||||
}
|
||||
|
||||
// Start music at lower volume
|
||||
startDeathMusic(0.2, false);
|
||||
boyfriend.playAnimation('deathLoop' + animationSuffix);
|
||||
deathQuoteSound = FunkinSound.playOnce(deathQuote, function() {
|
||||
// Once the quote ends, fade in the game over music.
|
||||
if (!isEnding && gameOverMusic != null)
|
||||
{
|
||||
gameOverMusic.fadeIn(4, 0.2, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Do behavior which occurs when you confirm and move to restart the level.
|
||||
*/
|
||||
|
@ -339,6 +389,14 @@ class GameOverSubState extends MusicBeatSubState
|
|||
isEnding = true;
|
||||
startDeathMusic(1.0, true); // isEnding changes this function's behavior.
|
||||
|
||||
// Stop death quotes immediately.
|
||||
hasPlayedDeathQuote = true;
|
||||
if (deathQuoteSound != null)
|
||||
{
|
||||
deathQuoteSound.stop();
|
||||
deathQuoteSound = null;
|
||||
}
|
||||
|
||||
if (PlayState.instance.isMinimalMode || boyfriend == null) {}
|
||||
else
|
||||
{
|
||||
|
@ -487,26 +545,7 @@ class GameOverSubState extends MusicBeatSubState
|
|||
}
|
||||
}
|
||||
|
||||
var playingJeffQuote:Bool = false;
|
||||
|
||||
/**
|
||||
* Week 7-specific hardcoded behavior, to play a custom death quote.
|
||||
* TODO: Make this a module somehow.
|
||||
*/
|
||||
function playJeffQuote():Void
|
||||
{
|
||||
var randomCensor:Array<Int> = [];
|
||||
|
||||
if (!Preferences.naughtyness) randomCensor = [1, 3, 8, 13, 17, 21];
|
||||
|
||||
FunkinSound.playOnce(Paths.sound('jeffGameover/jeffGameover-' + FlxG.random.int(1, 25, randomCensor)), function() {
|
||||
// Once the quote ends, fade in the game over music.
|
||||
if (!isEnding && gameOverMusic != null)
|
||||
{
|
||||
gameOverMusic.fadeIn(4, 0.2, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
var hasPlayedDeathQuote:Bool = false;
|
||||
|
||||
public override function destroy():Void
|
||||
{
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
package funkin.play;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.play.PlayState.PlayStateParams;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.ui.MusicBeatState;
|
||||
import flixel.addons.transition.FlxTransitionableState;
|
||||
|
|
|
@ -1,27 +1,23 @@
|
|||
package funkin.play;
|
||||
|
||||
import flixel.FlxState;
|
||||
import funkin.ui.story.StoryMenuState;
|
||||
import funkin.data.freeplay.player.PlayerRegistry;
|
||||
import flixel.addons.transition.FlxTransitionableState;
|
||||
import flixel.FlxG;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
import flixel.group.FlxSpriteGroup;
|
||||
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
|
||||
import flixel.math.FlxMath;
|
||||
import flixel.sound.FlxSound;
|
||||
import flixel.text.FlxText;
|
||||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.audio.FunkinSound;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.ui.freeplay.FreeplayState;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.play.cutscene.VideoCutscene;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.ui.AtlasText;
|
||||
import funkin.ui.debug.latency.LatencyState;
|
||||
import funkin.ui.MusicBeatSubState;
|
||||
import funkin.ui.transition.StickerSubState;
|
||||
|
||||
/**
|
||||
* Parameters for initializing the PauseSubState.
|
||||
|
@ -126,7 +122,10 @@ class PauseSubState extends MusicBeatSubState
|
|||
* Disallow input until transitions are complete!
|
||||
* This prevents the pause menu from immediately closing when opened, among other things.
|
||||
*/
|
||||
public var allowInput:Bool = false;
|
||||
public var allowInput:Bool = true;
|
||||
|
||||
// If this is true, it means we are frame 1 of our substate.
|
||||
var justOpened:Bool = true;
|
||||
|
||||
/**
|
||||
* The entries currently displayed in the pause menu.
|
||||
|
@ -395,10 +394,6 @@ class PauseSubState extends MusicBeatSubState
|
|||
FlxTween.tween(child, {alpha: 1, y: child.y + 5}, 1.8, {ease: FlxEase.quartOut, startDelay: delay});
|
||||
delay += 0.1;
|
||||
}
|
||||
|
||||
new FlxTimer().start(0.2, (_) -> {
|
||||
allowInput = true;
|
||||
});
|
||||
}
|
||||
|
||||
// ===============
|
||||
|
@ -421,15 +416,18 @@ class PauseSubState extends MusicBeatSubState
|
|||
changeSelection(1);
|
||||
}
|
||||
|
||||
if (controls.ACCEPT)
|
||||
if (controls.ACCEPT && !justOpened)
|
||||
{
|
||||
currentMenuEntries[currentEntry].callback(this);
|
||||
}
|
||||
else if (controls.PAUSE)
|
||||
else if (controls.PAUSE && !justOpened)
|
||||
{
|
||||
resume(this);
|
||||
}
|
||||
|
||||
// we only want justOpened to be true for 1 single frame, when we first get into the pause menu substate
|
||||
justOpened = false;
|
||||
|
||||
#if FEATURE_DEBUG_FUNCTIONS
|
||||
// to pause the game and get screenshots easy, press H on pause menu!
|
||||
if (FlxG.keys.justPressed.H)
|
||||
|
@ -438,6 +436,7 @@ class PauseSubState extends MusicBeatSubState
|
|||
|
||||
metadata.visible = visible;
|
||||
menuEntryText.visible = visible;
|
||||
background.visible = visible;
|
||||
this.bgColor = visible ? 0x99000000 : 0x00000000; // 60% or fully transparent black
|
||||
}
|
||||
#end
|
||||
|
@ -738,15 +737,25 @@ class PauseSubState extends MusicBeatSubState
|
|||
FlxTransitionableState.skipNextTransIn = true;
|
||||
FlxTransitionableState.skipNextTransOut = true;
|
||||
|
||||
var targetState:funkin.ui.transition.StickerSubState->FlxState = (PlayStatePlaylist.isStoryMode) ? (sticker) -> new StoryMenuState(sticker) : (sticker) ->
|
||||
FreeplayState.build(sticker);
|
||||
|
||||
// Do this AFTER because this resets the value of isStoryMode!
|
||||
if (PlayStatePlaylist.isStoryMode)
|
||||
{
|
||||
PlayStatePlaylist.reset();
|
||||
state.openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new funkin.ui.story.StoryMenuState(sticker)));
|
||||
}
|
||||
else
|
||||
|
||||
var playerCharacterId = PlayerRegistry.instance.getCharacterOwnerId(PlayState.instance.currentChart.characters.player);
|
||||
var stickerSet = (playerCharacterId == "pico") ? "stickers-set-2" : "stickers-set-1";
|
||||
var stickerPack = switch (PlayState.instance.currentChart.song.id)
|
||||
{
|
||||
state.openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> FreeplayState.build(null, sticker)));
|
||||
}
|
||||
case "tutorial": "tutorial";
|
||||
case "darnell" | "lit-up" | "2hot": "weekend";
|
||||
default: "all";
|
||||
};
|
||||
|
||||
state.openSubState(new funkin.ui.transition.StickerSubState({targetState: targetState, stickerSet: stickerSet, stickerPack: stickerPack}));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -13,7 +13,6 @@ import flixel.ui.FlxBar;
|
|||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxStringUtil;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.api.newgrounds.NGio;
|
||||
import funkin.audio.FunkinSound;
|
||||
import funkin.audio.VoicesGroup;
|
||||
import funkin.data.dialogue.conversation.ConversationRegistry;
|
||||
|
@ -29,6 +28,7 @@ import funkin.graphics.FunkinSprite;
|
|||
import funkin.Highscore.Tallies;
|
||||
import funkin.input.PreciseInputManager;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.api.newgrounds.Events;
|
||||
import funkin.modding.events.ScriptEventDispatcher;
|
||||
import funkin.play.character.BaseCharacter;
|
||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
|
@ -58,6 +58,10 @@ import haxe.Int64;
|
|||
#if FEATURE_DISCORD_RPC
|
||||
import funkin.api.discord.DiscordClient;
|
||||
#end
|
||||
#if FEATURE_NEWGROUNDS
|
||||
import funkin.api.newgrounds.Medals;
|
||||
import funkin.api.newgrounds.Leaderboards;
|
||||
#end
|
||||
|
||||
/**
|
||||
* Parameters used to initialize the PlayState.
|
||||
|
@ -328,7 +332,8 @@ class PlayState extends MusicBeatSubState
|
|||
public var isInCutscene:Bool = false;
|
||||
|
||||
/**
|
||||
* Whether the inputs should be disabled for whatever reason... used for the stage edit lol!
|
||||
* Whether the inputs should be disabled for whatever reason...
|
||||
* Used after the song ends, and in the Stage Editor.
|
||||
*/
|
||||
public var disableKeys:Bool = false;
|
||||
|
||||
|
@ -358,13 +363,13 @@ class PlayState extends MusicBeatSubState
|
|||
|
||||
/**
|
||||
* Key press inputs which have been received but not yet processed.
|
||||
* These are encoded with an OS timestamp, so they
|
||||
* These are encoded with an OS timestamp, so we can account for input latency.
|
||||
**/
|
||||
var inputPressQueue:Array<PreciseInputEvent> = [];
|
||||
|
||||
/**
|
||||
* Key release inputs which have been received but not yet processed.
|
||||
* These are encoded with an OS timestamp, so they
|
||||
* These are encoded with an OS timestamp, so we can account for input latency.
|
||||
**/
|
||||
var inputReleaseQueue:Array<PreciseInputEvent> = [];
|
||||
|
||||
|
@ -802,8 +807,6 @@ class PlayState extends MusicBeatSubState
|
|||
|
||||
public override function update(elapsed:Float):Void
|
||||
{
|
||||
// TOTAL: 9.42% CPU Time when profiled in VS 2019.
|
||||
|
||||
if (criticalFailure) return;
|
||||
|
||||
super.update(elapsed);
|
||||
|
@ -888,6 +891,10 @@ class PlayState extends MusicBeatSubState
|
|||
Highscore.tallies.combo = 0;
|
||||
Countdown.performCountdown();
|
||||
|
||||
// Reset the health icons.
|
||||
currentStage.getBoyfriend().initHealthIcon(false);
|
||||
currentStage.getDad().initHealthIcon(true);
|
||||
|
||||
needsReset = false;
|
||||
}
|
||||
|
||||
|
@ -917,6 +924,13 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
Conductor.instance.update(Conductor.instance.songPosition + elapsed * 1000, false); // Normal conductor update.
|
||||
|
||||
// If, after updating the conductor, the instrumental has finished, end the song immediately.
|
||||
// This helps prevent a major bug where the level suddenly loops back to the start or middle.
|
||||
if (Conductor.instance.songPosition >= (FlxG.sound.music.endTime ?? FlxG.sound.music.length))
|
||||
{
|
||||
if (mayPauseGame) endSong(skipEndingTransition);
|
||||
}
|
||||
}
|
||||
|
||||
var androidPause:Bool = false;
|
||||
|
@ -1033,6 +1047,9 @@ class PlayState extends MusicBeatSubState
|
|||
if (FlxG.sound.music != null) FlxG.sound.music.pause();
|
||||
|
||||
deathCounter += 1;
|
||||
#if FEATURE_NEWGROUNDS
|
||||
Events.logFailSong(currentSong.id, currentVariation);
|
||||
#end
|
||||
|
||||
dispatchEvent(new ScriptEvent(GAME_OVER));
|
||||
|
||||
|
@ -1114,8 +1131,8 @@ class PlayState extends MusicBeatSubState
|
|||
|
||||
if (!isMinimalMode)
|
||||
{
|
||||
iconP1.updatePosition();
|
||||
iconP2.updatePosition();
|
||||
if (iconP1 != null) iconP1.updatePosition();
|
||||
if (iconP1 != null) iconP2.updatePosition();
|
||||
}
|
||||
|
||||
// Transition to the game over substate.
|
||||
|
@ -1194,7 +1211,7 @@ class PlayState extends MusicBeatSubState
|
|||
{
|
||||
// If there is a substate which requires the game to continue,
|
||||
// then make this a condition.
|
||||
var shouldPause = (Std.isOfType(subState, PauseSubState) || Std.isOfType(subState, GameOverSubState));
|
||||
var shouldPause:Bool = (Std.isOfType(subState, PauseSubState) || Std.isOfType(subState, GameOverSubState));
|
||||
|
||||
if (shouldPause)
|
||||
{
|
||||
|
@ -1440,7 +1457,7 @@ class PlayState extends MusicBeatSubState
|
|||
var playerVoicesError:Float = 0;
|
||||
var opponentVoicesError:Float = 0;
|
||||
|
||||
if (vocals != null)
|
||||
if (vocals != null && vocals.playing)
|
||||
{
|
||||
@:privateAccess // todo: maybe make the groups public :thinking:
|
||||
{
|
||||
|
@ -2059,7 +2076,7 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
FlxG.sound.music.onComplete = function() {
|
||||
endSong(skipEndingTransition);
|
||||
if (mayPauseGame) endSong(skipEndingTransition);
|
||||
};
|
||||
// A negative instrumental offset means the song skips the first few milliseconds of the track.
|
||||
// This just gets added into the startTimestamp behavior so we don't need to do anything extra.
|
||||
|
@ -2100,6 +2117,10 @@ class PlayState extends MusicBeatSubState
|
|||
}
|
||||
|
||||
dispatchEvent(new ScriptEvent(SONG_START));
|
||||
|
||||
#if FEATURE_NEWGROUNDS
|
||||
Events.logStartSong(currentSong.id, currentVariation);
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2906,6 +2927,9 @@ class PlayState extends MusicBeatSubState
|
|||
vocals.volume = 0;
|
||||
mayPauseGame = false;
|
||||
|
||||
// Prevent ghost misses while the song is ending.
|
||||
disableKeys = true;
|
||||
|
||||
// Check if any events want to prevent the song from ending.
|
||||
var event = new ScriptEvent(SONG_END, true);
|
||||
dispatchEvent(event);
|
||||
|
@ -2951,8 +2975,16 @@ class PlayState extends MusicBeatSubState
|
|||
// adds current song data into the tallies for the level (story levels)
|
||||
Highscore.talliesLevel = Highscore.combineTallies(Highscore.tallies, Highscore.talliesLevel);
|
||||
|
||||
#if FEATURE_NEWGROUNDS
|
||||
Leaderboards.submitSongScore(currentSong.id, suffixedDifficulty, data.score);
|
||||
#end
|
||||
|
||||
if (!isPracticeMode && !isBotPlayMode)
|
||||
{
|
||||
#if FEATURE_NEWGROUNDS
|
||||
Events.logCompleteSong(currentSong.id, currentVariation);
|
||||
#end
|
||||
|
||||
isNewHighscore = Save.instance.isSongHighScore(currentSong.id, suffixedDifficulty, data);
|
||||
|
||||
// If no high score is present, save both score and rank.
|
||||
|
@ -2960,15 +2992,48 @@ class PlayState extends MusicBeatSubState
|
|||
// If neither are higher, nothing will change.
|
||||
Save.instance.applySongRank(currentSong.id, suffixedDifficulty, data);
|
||||
|
||||
if (isNewHighscore)
|
||||
{
|
||||
#if newgrounds
|
||||
NGio.postScore(score, currentSong.id);
|
||||
#end
|
||||
}
|
||||
if (isNewHighscore) {}
|
||||
}
|
||||
}
|
||||
|
||||
#if FEATURE_NEWGROUNDS
|
||||
// Only award medals if we are LEGIT.
|
||||
if (!isPracticeMode && !isBotPlayMode && !isChartingMode && currentSong.validScore)
|
||||
{
|
||||
// Award a medal for beating at least one song on any difficulty on a Friday.
|
||||
if (Date.now().getDay() == 5) Medals.award(FridayNight);
|
||||
|
||||
// Determine the score rank for this song we just finished.
|
||||
var scoreRank:ScoringRank = Scoring.calculateRank(
|
||||
{
|
||||
score: songScore,
|
||||
tallies:
|
||||
{
|
||||
sick: Highscore.tallies.sick,
|
||||
good: Highscore.tallies.good,
|
||||
bad: Highscore.tallies.bad,
|
||||
shit: Highscore.tallies.shit,
|
||||
missed: Highscore.tallies.missed,
|
||||
combo: Highscore.tallies.combo,
|
||||
maxCombo: Highscore.tallies.maxCombo,
|
||||
totalNotesHit: Highscore.tallies.totalNotesHit,
|
||||
totalNotes: Highscore.tallies.totalNotes,
|
||||
}
|
||||
});
|
||||
|
||||
// Award various medals based on variation, difficulty, song ID, and scoring rank.
|
||||
if (scoreRank == ScoringRank.SHIT) Medals.award(LossRating);
|
||||
if (scoreRank >= ScoringRank.PERFECT && currentDifficulty == 'hard') Medals.award(PerfectRatingHard);
|
||||
if (scoreRank == ScoringRank.PERFECT_GOLD && currentDifficulty == 'hard') Medals.award(GoldPerfectRatingHard);
|
||||
if (Constants.DEFAULT_DIFFICULTY_LIST_ERECT.contains(currentDifficulty)) Medals.award(ErectDifficulty);
|
||||
if (scoreRank == ScoringRank.PERFECT_GOLD && currentDifficulty == 'nightmare') Medals.award(GoldPerfectRatingNightmare);
|
||||
if (currentVariation == 'pico' && !PlayStatePlaylist.isStoryMode) Medals.award(FreeplayPicoMix);
|
||||
if (currentVariation == 'pico' && currentSong.id == 'stress') Medals.award(FreeplayStressPico);
|
||||
|
||||
Events.logEarnRank(scoreRank.toString());
|
||||
}
|
||||
#end
|
||||
|
||||
if (PlayStatePlaylist.isStoryMode)
|
||||
{
|
||||
isNewHighscore = false;
|
||||
|
@ -2983,8 +3048,6 @@ class PlayState extends MusicBeatSubState
|
|||
{
|
||||
if (currentSong.validScore)
|
||||
{
|
||||
NGio.unlockMedal(60961);
|
||||
|
||||
var data =
|
||||
{
|
||||
score: PlayStatePlaylist.campaignScore,
|
||||
|
@ -3003,12 +3066,19 @@ class PlayState extends MusicBeatSubState
|
|||
},
|
||||
};
|
||||
|
||||
#if FEATURE_NEWGROUNDS
|
||||
// Award a medal for beating a Story level.
|
||||
Medals.awardStoryLevel(PlayStatePlaylist.campaignId);
|
||||
|
||||
// Submit the score for the Story level to Newgrounds.
|
||||
Leaderboards.submitLevelScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, PlayStatePlaylist.campaignScore);
|
||||
|
||||
Events.logCompleteLevel(PlayStatePlaylist.campaignId);
|
||||
#end
|
||||
|
||||
if (Save.instance.isLevelHighScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, data))
|
||||
{
|
||||
Save.instance.setLevelScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, data);
|
||||
#if newgrounds
|
||||
NGio.postScore(score, 'Level ${PlayStatePlaylist.campaignId}');
|
||||
#end
|
||||
isNewHighscore = true;
|
||||
}
|
||||
}
|
||||
|
@ -3283,7 +3353,9 @@ class PlayState extends MusicBeatSubState
|
|||
totalNotes: talliesToUse.totalNotes,
|
||||
},
|
||||
},
|
||||
isNewHighscore: isNewHighscore
|
||||
isNewHighscore: isNewHighscore,
|
||||
isPracticeMode: isPracticeMode,
|
||||
isBotPlayMode: isBotPlayMode,
|
||||
});
|
||||
this.persistentDraw = false;
|
||||
openSubState(res);
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
package funkin.play;
|
||||
|
||||
import funkin.util.MathUtil;
|
||||
import funkin.ui.story.StoryMenuState;
|
||||
import funkin.graphics.adobeanimate.FlxAtlasSprite;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.FlxState;
|
||||
import flixel.FlxSubState;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import flixel.effects.FlxFlicker;
|
||||
|
@ -14,27 +12,26 @@ import flixel.math.FlxPoint;
|
|||
import funkin.ui.MusicBeatSubState;
|
||||
import flixel.math.FlxRect;
|
||||
import flixel.text.FlxBitmapText;
|
||||
import funkin.ui.freeplay.FreeplayScore;
|
||||
import flixel.text.FlxText;
|
||||
import funkin.data.freeplay.player.PlayerRegistry;
|
||||
import funkin.data.freeplay.player.PlayerData;
|
||||
import funkin.data.freeplay.player.PlayerData.PlayerResultsAnimationData;
|
||||
import funkin.ui.freeplay.charselect.PlayableCharacter;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.tweens.FlxEase;
|
||||
import funkin.graphics.FunkinCamera;
|
||||
import funkin.input.Controls;
|
||||
import funkin.ui.freeplay.FreeplayState;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.addons.display.FlxBackdrop;
|
||||
import funkin.audio.FunkinSound;
|
||||
import flixel.util.FlxGradient;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.save.Save;
|
||||
import funkin.play.scoring.Scoring;
|
||||
import funkin.save.Save.SaveScoreData;
|
||||
import funkin.graphics.shaders.LeftMaskShader;
|
||||
import funkin.play.components.TallyCounter;
|
||||
import funkin.play.components.ClearPercentCounter;
|
||||
#if FEATURE_NEWGROUNDS
|
||||
import funkin.api.newgrounds.Medals;
|
||||
#end
|
||||
|
||||
/**
|
||||
* The state for the results screen after a song or week is finished.
|
||||
|
@ -65,7 +62,9 @@ class ResultState extends MusicBeatSubState
|
|||
{
|
||||
sprite:FlxAtlasSprite,
|
||||
delay:Float,
|
||||
forceLoop:Bool
|
||||
forceLoop:Bool,
|
||||
startFrameLabel:String,
|
||||
sound:String
|
||||
}> = [];
|
||||
var characterSparrowAnimations:Array<
|
||||
{
|
||||
|
@ -182,6 +181,11 @@ class ResultState extends MusicBeatSubState
|
|||
{
|
||||
if (animData == null) continue;
|
||||
|
||||
if (animData.filter != "both")
|
||||
{
|
||||
if (Preferences.naughtyness && animData.filter != "naughty" || !Preferences.naughtyness && animData.filter != "safe") continue;
|
||||
}
|
||||
|
||||
var animPath:String = Paths.stripLibrary(animData.assetPath);
|
||||
var animLibrary:String = Paths.getLibrary(animData.assetPath);
|
||||
var offsets = animData.offsets ?? [0, 0];
|
||||
|
@ -197,7 +201,6 @@ class ResultState extends MusicBeatSubState
|
|||
{
|
||||
// Animation is not looped.
|
||||
animation.onAnimationComplete.add((_name:String) -> {
|
||||
trace("AHAHAH 2");
|
||||
if (animation != null)
|
||||
{
|
||||
animation.anim.pause();
|
||||
|
@ -207,7 +210,6 @@ class ResultState extends MusicBeatSubState
|
|||
else if (animData.loopFrameLabel != null)
|
||||
{
|
||||
animation.onAnimationComplete.add((_name:String) -> {
|
||||
trace("AHAHAH 2");
|
||||
if (animation != null)
|
||||
{
|
||||
animation.playAnimation(animData.loopFrameLabel ?? '', true, false, true); // unpauses this anim, since it's on PlayOnce!
|
||||
|
@ -225,7 +227,6 @@ class ResultState extends MusicBeatSubState
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Hide until ready to play.
|
||||
animation.visible = false;
|
||||
// Queue to play.
|
||||
|
@ -233,7 +234,9 @@ class ResultState extends MusicBeatSubState
|
|||
{
|
||||
sprite: animation,
|
||||
delay: animData.delay ?? 0.0,
|
||||
forceLoop: (animData.loopFrame ?? -1) == 0
|
||||
forceLoop: (animData.loopFrame ?? -1) == 0,
|
||||
startFrameLabel: (animData.startFrameLabel ?? ""),
|
||||
sound: (animData.sound ?? "")
|
||||
});
|
||||
// Add to the scene.
|
||||
add(animation);
|
||||
|
@ -366,6 +369,12 @@ class ResultState extends MusicBeatSubState
|
|||
var maxCombo:TallyCounter = new TallyCounter(375, hStuf * 4, params.scoreData.tallies.maxCombo);
|
||||
ratingGrp.add(maxCombo);
|
||||
|
||||
if (params.scoreData.tallies.totalNotesHit >= 1000)
|
||||
{
|
||||
totalHit.x -= 30;
|
||||
maxCombo.x -= 30;
|
||||
}
|
||||
|
||||
hStuf += 2;
|
||||
var extraYOffset:Float = 7;
|
||||
|
||||
|
@ -492,6 +501,12 @@ class ResultState extends MusicBeatSubState
|
|||
// Just to be sure that the lerp didn't mess things up.
|
||||
clearPercentCounter.curNumber = clearPercentTarget;
|
||||
|
||||
#if FEATURE_NEWGROUNDS
|
||||
var isScoreValid = !(params?.isPracticeMode ?? false) && !(params?.isBotPlayMode ?? false);
|
||||
// This is the easiest spot to do the medal calculation lol.
|
||||
if (isScoreValid && clearPercentTarget == 69) Medals.award(Nice);
|
||||
#end
|
||||
|
||||
clearPercentCounter.flash(true);
|
||||
new FlxTimer().start(0.4, _ -> {
|
||||
clearPercentCounter.flash(false);
|
||||
|
@ -591,7 +606,14 @@ class ResultState extends MusicBeatSubState
|
|||
new FlxTimer().start(atlas.delay, _ -> {
|
||||
if (atlas.sprite == null) return;
|
||||
atlas.sprite.visible = true;
|
||||
atlas.sprite.playAnimation('');
|
||||
atlas.sprite.playAnimation(atlas.startFrameLabel);
|
||||
if (atlas.sound != "")
|
||||
{
|
||||
var sndPath:String = Paths.stripLibrary(atlas.sound);
|
||||
var sndLibrary:String = Paths.getLibrary(atlas.sound);
|
||||
|
||||
FunkinSound.playOnce(Paths.sound(sndPath, sndLibrary), 1.0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -676,33 +698,6 @@ class ResultState extends MusicBeatSubState
|
|||
|
||||
override function update(elapsed:Float):Void
|
||||
{
|
||||
// if(FlxG.keys.justPressed.R){
|
||||
// FlxG.switchState(() -> new funkin.play.ResultState(
|
||||
// {
|
||||
// storyMode: false,
|
||||
// title: "Cum Song Erect by Kawai Sprite",
|
||||
// songId: "cum",
|
||||
// difficultyId: "nightmare",
|
||||
// isNewHighscore: true,
|
||||
// scoreData:
|
||||
// {
|
||||
// score: 1_234_567,
|
||||
// tallies:
|
||||
// {
|
||||
// sick: 200,
|
||||
// good: 0,
|
||||
// bad: 0,
|
||||
// shit: 0,
|
||||
// missed: 0,
|
||||
// combo: 0,
|
||||
// maxCombo: 69,
|
||||
// totalNotesHit: 200,
|
||||
// totalNotes: 200 // 0,
|
||||
// }
|
||||
// },
|
||||
// }));
|
||||
// }
|
||||
|
||||
// maskShaderSongName.swagSprX = songName.x;
|
||||
maskShaderDifficulty.swagSprX = difficulty.x;
|
||||
|
||||
|
@ -721,15 +716,10 @@ class ResultState extends MusicBeatSubState
|
|||
}
|
||||
}
|
||||
|
||||
if (FlxG.keys.justPressed.RIGHT) speedOfTween.x += 0.1;
|
||||
|
||||
if (FlxG.keys.justPressed.LEFT)
|
||||
{
|
||||
speedOfTween.x -= 0.1;
|
||||
}
|
||||
|
||||
if (controls.PAUSE || controls.ACCEPT)
|
||||
{
|
||||
if (_parentState is funkin.ui.debug.results.ResultsDebugSubState)
|
||||
close(); // IF we are a substate, we will close ourselves. This is used from ResultsDebugSubState
|
||||
if (introMusicAudio != null)
|
||||
{
|
||||
@:nullSafety(Off)
|
||||
|
@ -776,6 +766,14 @@ class ResultState extends MusicBeatSubState
|
|||
var shouldTween = false;
|
||||
var shouldUseSubstate = false;
|
||||
|
||||
var stickerSet = (playerCharacterId == "pico") ? "stickers-set-2" : "stickers-set-1";
|
||||
var stickerPack = switch (PlayState.instance?.currentChart?.song?.id)
|
||||
{
|
||||
case "tutorial": "tutorial";
|
||||
case "darnell" | "lit-up" | "2hot": "weekend";
|
||||
default: "all";
|
||||
};
|
||||
|
||||
if (params.storyMode)
|
||||
{
|
||||
if (PlayerRegistry.instance.hasNewCharacter())
|
||||
|
@ -797,12 +795,21 @@ class ResultState extends MusicBeatSubState
|
|||
// No new characters.
|
||||
shouldTween = false;
|
||||
shouldUseSubstate = true;
|
||||
targetState = new funkin.ui.transition.StickerSubState(null, (sticker) -> new StoryMenuState(sticker));
|
||||
targetState = new funkin.ui.transition.StickerSubState(
|
||||
{
|
||||
targetState: (sticker) -> new StoryMenuState(sticker),
|
||||
stickerSet: stickerSet,
|
||||
stickerPack: stickerPack
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (rank > Scoring.calculateRank(params?.prevScoreData))
|
||||
var isScoreValid = !(params?.isPracticeMode ?? false) && !(params?.isBotPlayMode ?? false);
|
||||
|
||||
var isPersonalBest = rank > Scoring.calculateRank(params?.prevScoreData);
|
||||
|
||||
if (isScoreValid && isPersonalBest)
|
||||
{
|
||||
trace('THE RANK IS Higher.....');
|
||||
|
||||
|
@ -826,7 +833,12 @@ class ResultState extends MusicBeatSubState
|
|||
{
|
||||
shouldTween = false;
|
||||
shouldUseSubstate = true;
|
||||
targetState = new funkin.ui.transition.StickerSubState(null, (sticker) -> FreeplayState.build(null, sticker));
|
||||
targetState = new funkin.ui.transition.StickerSubState(
|
||||
{
|
||||
targetState: (sticker) -> FreeplayState.build(null, sticker),
|
||||
stickerSet: stickerSet,
|
||||
stickerPack: stickerPack
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -893,6 +905,16 @@ typedef ResultsStateParams =
|
|||
*/
|
||||
var ?isNewHighscore:Bool;
|
||||
|
||||
/**
|
||||
* Whether the displayed score is from a song played with Practice Mode enabled.
|
||||
*/
|
||||
var ?isPracticeMode:Bool;
|
||||
|
||||
/**
|
||||
* Whether the displayed score is from a song played with Bot Play Mode enabled.
|
||||
*/
|
||||
var ?isBotPlayMode:Bool;
|
||||
|
||||
/**
|
||||
* The difficulty ID of the song/week we just played.
|
||||
* @default Normal
|
||||
|
|
|
@ -16,6 +16,7 @@ import flixel.util.FlxDestroyUtil;
|
|||
import funkin.graphics.adobeanimate.FlxAtlasSprite;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.play.character.CharacterData.CharacterRenderType;
|
||||
import flixel.util.FlxDirectionFlags;
|
||||
import openfl.display.BitmapData;
|
||||
import openfl.display.BlendMode;
|
||||
|
||||
|
@ -74,10 +75,14 @@ class AnimateAtlasCharacter extends BaseCharacter
|
|||
|
||||
override function onCreate(event:ScriptEvent):Void
|
||||
{
|
||||
trace('Creating Animate Atlas character: ' + this.characterId);
|
||||
// Display a custom scope for debugging purposes.
|
||||
#if FEATURE_DEBUG_TRACY
|
||||
cpp.vm.tracy.TracyProfiler.zoneScoped('AnimateAtlasCharacter.create(${this.characterId})');
|
||||
#end
|
||||
|
||||
try
|
||||
{
|
||||
trace('Loading assets for Animate Atlas character "${characterId}"', flixel.util.FlxColor.fromString("#89CFF0"));
|
||||
var atlasSprite:FlxAtlasSprite = loadAtlasSprite();
|
||||
setSprite(atlasSprite);
|
||||
|
||||
|
@ -93,12 +98,10 @@ class AnimateAtlasCharacter extends BaseCharacter
|
|||
|
||||
public override function playAnimation(name:String, restart:Bool = false, ignoreOther:Bool = false, reverse:Bool = false):Void
|
||||
{
|
||||
if ((!canPlayOtherAnims && !ignoreOther)) return;
|
||||
|
||||
var correctName = correctAnimationName(name);
|
||||
if (correctName == null)
|
||||
{
|
||||
trace('Could not find Atlas animation: ' + name);
|
||||
trace('$characterName Could not find Atlas animation: ' + name);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -145,9 +148,16 @@ class AnimateAtlasCharacter extends BaseCharacter
|
|||
{
|
||||
super.onAnimationFinished(prefix);
|
||||
|
||||
if (!getCurrentAnimation().endsWith(Constants.ANIMATION_HOLD_SUFFIX)
|
||||
&& hasAnimation(getCurrentAnimation() + Constants.ANIMATION_HOLD_SUFFIX))
|
||||
{
|
||||
playAnimation(getCurrentAnimation() + Constants.ANIMATION_HOLD_SUFFIX);
|
||||
}
|
||||
|
||||
if (getAnimationData() != null && getAnimationData().looped)
|
||||
{
|
||||
playAnimation(currentAnimName, true, false);
|
||||
if (StringTools.endsWith(prefix, "-hold")) trace(prefix);
|
||||
playAnimation(prefix, true, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -380,7 +390,7 @@ class AnimateAtlasCharacter extends BaseCharacter
|
|||
inline function directAlphaTransform(sprite:FlxSprite, alpha:Float):Void
|
||||
sprite.alpha = alpha; // direct set
|
||||
|
||||
inline function facingTransform(sprite:FlxSprite, facing:Int):Void
|
||||
inline function facingTransform(sprite:FlxSprite, facing:FlxDirectionFlags):Void
|
||||
sprite.facing = facing;
|
||||
|
||||
inline function flipXTransform(sprite:FlxSprite, flipX:Bool):Void
|
||||
|
@ -449,6 +459,57 @@ class AnimateAtlasCharacter extends BaseCharacter
|
|||
}
|
||||
}
|
||||
|
||||
var resS:FlxPoint = new FlxPoint();
|
||||
|
||||
/**
|
||||
* Reset the character so it can be used at the start of the level.
|
||||
* Call this when restarting the level.
|
||||
*/
|
||||
override public function resetCharacter(resetCamera:Bool = true):Void
|
||||
{
|
||||
trace("RESETTING ATLAS " + characterName);
|
||||
|
||||
// Reset the animation offsets. This will modify x and y to be the absolute position of the character.
|
||||
// this.animOffsets = [0, 0];
|
||||
|
||||
// Now we can set the x and y to be their original values without having to account for animOffsets.
|
||||
this.resetPosition();
|
||||
mainSprite.setPosition(originalPosition.x, originalPosition.y);
|
||||
|
||||
// Then reapply animOffsets...
|
||||
// applyAnimationOffsets(getCurrentAnimation());
|
||||
|
||||
// Make sure we are playing the idle animation
|
||||
// ...then update the hitbox so that this.width and this.height are correct.
|
||||
|
||||
mainSprite.scale.set(1, 1);
|
||||
mainSprite.alpha = 0.0001;
|
||||
mainSprite.width = 0;
|
||||
mainSprite.height = 0;
|
||||
this.dance(true); // Force to avoid the old animation playing with the wrong offset at the start of the song.
|
||||
|
||||
mainSprite.draw(); // refresh frame
|
||||
|
||||
if (resS.x == 0)
|
||||
{
|
||||
resS.x = mainSprite.width; // clunky bizz
|
||||
resS.y = mainSprite.height;
|
||||
}
|
||||
|
||||
mainSprite.alpha = alpha;
|
||||
|
||||
mainSprite.width = resS.x;
|
||||
mainSprite.height = resS.y;
|
||||
frameWidth = 0;
|
||||
frameHeight = 0;
|
||||
|
||||
scaleCallback(scale);
|
||||
this.updateHitbox();
|
||||
|
||||
// Reset the camera focus point while we're at it.
|
||||
if (resetCamera) this.resetCameraFocusPoint();
|
||||
}
|
||||
|
||||
inline function offsetCallback(offset:FlxPoint):Void
|
||||
transformChildren(offsetTransform, offset);
|
||||
|
||||
|
@ -528,7 +589,7 @@ class AnimateAtlasCharacter extends BaseCharacter
|
|||
return alpha = value;
|
||||
}
|
||||
|
||||
override function set_facing(value:Int):Int
|
||||
override function set_facing(value:FlxDirectionFlags):FlxDirectionFlags
|
||||
{
|
||||
if (exists && facing != value) transformChildren(facingTransform, value);
|
||||
return facing = value;
|
||||
|
|
|
@ -149,6 +149,7 @@ class BaseCharacter extends Bopper
|
|||
public function new(id:String, renderType:CharacterRenderType)
|
||||
{
|
||||
super(CharacterDataParser.DEFAULT_DANCEEVERY);
|
||||
|
||||
this.characterId = id;
|
||||
|
||||
ignoreExclusionPref = ["sing"];
|
||||
|
@ -643,6 +644,11 @@ class BaseCharacter extends Bopper
|
|||
{
|
||||
super.playAnimation(name, restart, ignoreOther, reversed);
|
||||
}
|
||||
|
||||
public function getDeathQuote():Null<String>
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -296,7 +296,7 @@ class CharacterDataParser
|
|||
charPath += "monsterpixel";
|
||||
case "mom" | "mom-car":
|
||||
charPath += "mommypixel";
|
||||
case "pico-blazin" | "pico-playable" | "pico-speaker":
|
||||
case "pico-blazin" | "pico-playable" | "pico-speaker" | "pico-pixel" | "pico-holding-nene":
|
||||
charPath += "picopixel";
|
||||
case "gf-christmas" | "gf-car" | "gf-pixel" | "gf-tankmen" | "gf-dark":
|
||||
charPath += "gfpixel";
|
||||
|
@ -308,7 +308,7 @@ class CharacterDataParser
|
|||
charPath += "senpaipixel";
|
||||
case "spooky-dark":
|
||||
charPath += "spookypixel";
|
||||
case "tankman-atlas":
|
||||
case "tankman-atlas" | "tankman-bloody":
|
||||
charPath += "tankmanpixel";
|
||||
case "pico-christmas" | "pico-dark":
|
||||
charPath += "picopixel";
|
||||
|
|
|
@ -26,7 +26,10 @@ class MultiSparrowCharacter extends BaseCharacter
|
|||
|
||||
override function onCreate(event:ScriptEvent):Void
|
||||
{
|
||||
trace('Creating Multi-Sparrow character: ' + this.characterId);
|
||||
// Display a custom scope for debugging purposes.
|
||||
#if FEATURE_DEBUG_TRACY
|
||||
cpp.vm.tracy.TracyProfiler.zoneScoped('MultiSparrowCharacter.create(${this.characterId})');
|
||||
#end
|
||||
|
||||
buildSprites();
|
||||
super.onCreate(event);
|
||||
|
@ -41,8 +44,8 @@ class MultiSparrowCharacter extends BaseCharacter
|
|||
{
|
||||
this.isPixel = true;
|
||||
this.antialiasing = false;
|
||||
pixelPerfectRender = true;
|
||||
pixelPerfectPosition = true;
|
||||
// pixelPerfectRender = true;
|
||||
// pixelPerfectPosition = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -53,6 +56,8 @@ class MultiSparrowCharacter extends BaseCharacter
|
|||
|
||||
function buildSpritesheet():Void
|
||||
{
|
||||
trace('Loading assets for Multi-Sparrow character "${characterId}"', flixel.util.FlxColor.fromString("#89CFF0"));
|
||||
|
||||
var assetList = [];
|
||||
for (anim in _data.animations)
|
||||
{
|
||||
|
@ -123,10 +128,6 @@ class MultiSparrowCharacter extends BaseCharacter
|
|||
|
||||
public override function playAnimation(name:String, restart:Bool = false, ignoreOther:Bool = false, reverse:Bool = false):Void
|
||||
{
|
||||
// Make sure we ignore other animations if we're currently playing a forced one,
|
||||
// unless we're forcing a new animation.
|
||||
if (!this.canPlayOtherAnims && !ignoreOther) return;
|
||||
|
||||
super.playAnimation(name, restart, ignoreOther, reverse);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,10 @@ class PackerCharacter extends BaseCharacter
|
|||
|
||||
override function onCreate(event:ScriptEvent):Void
|
||||
{
|
||||
trace('Creating Packer character: ' + this.characterId);
|
||||
// Display a custom scope for debugging purposes.
|
||||
#if FEATURE_DEBUG_TRACY
|
||||
cpp.vm.tracy.TracyProfiler.zoneScoped('PackerCharacter.create(${this.characterId})');
|
||||
#end
|
||||
|
||||
loadSpritesheet();
|
||||
loadAnimations();
|
||||
|
@ -28,7 +31,7 @@ class PackerCharacter extends BaseCharacter
|
|||
|
||||
function loadSpritesheet():Void
|
||||
{
|
||||
trace('[PACKERCHAR] Loading spritesheet ${_data.assetPath} for ${characterId}');
|
||||
trace('Loading assets for Packer character "${characterId}"', flixel.util.FlxColor.fromString("#89CFF0"));
|
||||
|
||||
var tex:FlxFramesCollection = Paths.getPackerAtlas(_data.assetPath);
|
||||
if (tex == null)
|
||||
|
@ -43,8 +46,8 @@ class PackerCharacter extends BaseCharacter
|
|||
{
|
||||
this.isPixel = true;
|
||||
this.antialiasing = false;
|
||||
pixelPerfectRender = true;
|
||||
pixelPerfectPosition = true;
|
||||
// pixelPerfectRender = true;
|
||||
// pixelPerfectPosition = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -21,7 +21,10 @@ class SparrowCharacter extends BaseCharacter
|
|||
|
||||
override function onCreate(event:ScriptEvent):Void
|
||||
{
|
||||
trace('Creating Sparrow character: ' + this.characterId);
|
||||
// Display a custom scope for debugging purposes.
|
||||
#if FEATURE_DEBUG_TRACY
|
||||
cpp.vm.tracy.TracyProfiler.zoneScoped('SparrowCharacter.create(${this.characterId})');
|
||||
#end
|
||||
|
||||
loadSpritesheet();
|
||||
loadAnimations();
|
||||
|
@ -31,7 +34,7 @@ class SparrowCharacter extends BaseCharacter
|
|||
|
||||
function loadSpritesheet()
|
||||
{
|
||||
trace('[SPARROWCHAR] Loading spritesheet ${_data.assetPath} for ${characterId}');
|
||||
trace('Loading assets for Sparrow character "${characterId}"', flixel.util.FlxColor.fromString("#89CFF0"));
|
||||
|
||||
var tex:FlxFramesCollection = Paths.getSparrowAtlas(_data.assetPath);
|
||||
if (tex == null)
|
||||
|
@ -46,8 +49,8 @@ class SparrowCharacter extends BaseCharacter
|
|||
{
|
||||
this.isPixel = true;
|
||||
this.antialiasing = false;
|
||||
pixelPerfectRender = true;
|
||||
pixelPerfectPosition = true;
|
||||
// pixelPerfectRender = true;
|
||||
// pixelPerfectPosition = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -3,13 +3,7 @@ package funkin.play.components;
|
|||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.graphics.shaders.PureColor;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
|
||||
import flixel.math.FlxMath;
|
||||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.text.FlxText.FlxTextAlign;
|
||||
import funkin.util.MathUtil;
|
||||
import flixel.util.FlxColor;
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,14 +1,9 @@
|
|||
package funkin.play.components;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.util.FlxDirection;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.play.PlayState;
|
||||
import funkin.util.TimerUtil;
|
||||
import funkin.util.EaseUtil;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.play.notes.notestyle.NoteStyle;
|
||||
|
||||
@:nullSafety
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
package funkin.play.components;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
|
||||
import flixel.math.FlxMath;
|
||||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.text.FlxText.FlxTextAlign;
|
||||
import funkin.util.MathUtil;
|
||||
|
||||
|
|
|
@ -6,11 +6,10 @@ import flixel.tweens.FlxEase;
|
|||
import flixel.tweens.FlxTween;
|
||||
import flixel.util.FlxColor;
|
||||
import flixel.util.FlxSignal;
|
||||
import flixel.util.FlxTimer;
|
||||
#if html5
|
||||
import funkin.graphics.video.FlxVideo;
|
||||
#end
|
||||
#if hxCodec
|
||||
#if hxvlc
|
||||
import funkin.graphics.video.FunkinVideoSprite;
|
||||
#end
|
||||
|
||||
|
@ -25,7 +24,7 @@ class VideoCutscene
|
|||
#if html5
|
||||
static var vid:FlxVideo;
|
||||
#end
|
||||
#if hxCodec
|
||||
#if hxvlc
|
||||
static var vid:FunkinVideoSprite;
|
||||
#end
|
||||
|
||||
|
@ -92,8 +91,8 @@ class VideoCutscene
|
|||
|
||||
#if html5
|
||||
playVideoHTML5(rawFilePath);
|
||||
#elseif hxCodec
|
||||
playVideoNative(rawFilePath);
|
||||
#elseif hxvlc
|
||||
playVideoNative(filePath);
|
||||
#else
|
||||
throw "No video support for this platform!";
|
||||
#end
|
||||
|
@ -101,7 +100,7 @@ class VideoCutscene
|
|||
|
||||
public static function isPlaying():Bool
|
||||
{
|
||||
#if (html5 || hxCodec)
|
||||
#if (html5 || hxvlc)
|
||||
return vid != null;
|
||||
#else
|
||||
return false;
|
||||
|
@ -134,7 +133,7 @@ class VideoCutscene
|
|||
}
|
||||
#end
|
||||
|
||||
#if hxCodec
|
||||
#if hxvlc
|
||||
static function playVideoNative(filePath:String):Void
|
||||
{
|
||||
// Video displays OVER the FlxState.
|
||||
|
@ -144,17 +143,18 @@ class VideoCutscene
|
|||
{
|
||||
vid.zIndex = 0;
|
||||
vid.bitmap.onEndReached.add(finishVideo.bind(0.5));
|
||||
vid.autoPause = FlxG.autoPause;
|
||||
|
||||
vid.cameras = [PlayState.instance.camCutscene];
|
||||
|
||||
PlayState.instance.add(vid);
|
||||
|
||||
PlayState.instance.refresh();
|
||||
vid.play(filePath, false);
|
||||
|
||||
if (vid.load(filePath)) vid.play();
|
||||
|
||||
// Resize videos bigger or smaller than the screen.
|
||||
vid.bitmap.onTextureSetup.add(() -> {
|
||||
vid.bitmap.onFormatSetup.add(function():Void {
|
||||
if (vid == null) return;
|
||||
vid.setGraphicSize(FlxG.width, FlxG.height);
|
||||
vid.updateHitbox();
|
||||
vid.x = 0;
|
||||
|
@ -181,7 +181,7 @@ class VideoCutscene
|
|||
}
|
||||
#end
|
||||
|
||||
#if hxCodec
|
||||
#if hxvlc
|
||||
if (vid != null)
|
||||
{
|
||||
// Seek to the start of the video.
|
||||
|
@ -207,7 +207,7 @@ class VideoCutscene
|
|||
}
|
||||
#end
|
||||
|
||||
#if hxCodec
|
||||
#if hxvlc
|
||||
if (vid != null)
|
||||
{
|
||||
vid.pause();
|
||||
|
@ -226,7 +226,7 @@ class VideoCutscene
|
|||
}
|
||||
#end
|
||||
|
||||
#if hxCodec
|
||||
#if hxvlc
|
||||
if (vid != null)
|
||||
{
|
||||
vid.visible = false;
|
||||
|
@ -245,7 +245,7 @@ class VideoCutscene
|
|||
}
|
||||
#end
|
||||
|
||||
#if hxCodec
|
||||
#if hxvlc
|
||||
if (vid != null)
|
||||
{
|
||||
vid.visible = true;
|
||||
|
@ -264,7 +264,7 @@ class VideoCutscene
|
|||
}
|
||||
#end
|
||||
|
||||
#if hxCodec
|
||||
#if hxvlc
|
||||
if (vid != null)
|
||||
{
|
||||
vid.resume();
|
||||
|
@ -291,7 +291,7 @@ class VideoCutscene
|
|||
}
|
||||
#end
|
||||
|
||||
#if hxCodec
|
||||
#if hxvlc
|
||||
if (vid != null)
|
||||
{
|
||||
vid.stop();
|
||||
|
@ -299,7 +299,7 @@ class VideoCutscene
|
|||
}
|
||||
#end
|
||||
|
||||
#if (html5 || hxCodec)
|
||||
#if (html5 || hxvlc)
|
||||
vid.destroy();
|
||||
vid = null;
|
||||
#end
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
package funkin.play.cutscene.dialogue;
|
||||
|
||||
import flixel.addons.display.FlxPieDial;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.group.FlxSpriteGroup;
|
||||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
|
@ -11,9 +9,7 @@ import funkin.audio.FunkinSound;
|
|||
import funkin.data.dialogue.conversation.ConversationData;
|
||||
import funkin.data.dialogue.conversation.ConversationData.DialogueEntryData;
|
||||
import funkin.data.dialogue.conversation.ConversationRegistry;
|
||||
import funkin.data.dialogue.dialoguebox.DialogueBoxData;
|
||||
import funkin.data.dialogue.dialoguebox.DialogueBoxRegistry;
|
||||
import funkin.data.dialogue.speaker.SpeakerData;
|
||||
import funkin.data.dialogue.speaker.SpeakerRegistry;
|
||||
import funkin.data.IRegistryEntry;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
|
@ -21,7 +17,6 @@ import funkin.modding.events.ScriptEvent;
|
|||
import funkin.modding.events.ScriptEventDispatcher;
|
||||
import funkin.modding.IScriptedClass.IDialogueScriptedClass;
|
||||
import funkin.modding.IScriptedClass.IEventHandler;
|
||||
import funkin.play.cutscene.dialogue.DialogueBox;
|
||||
import funkin.util.SortUtil;
|
||||
import funkin.util.EaseUtil;
|
||||
|
||||
|
@ -30,6 +25,7 @@ import funkin.util.EaseUtil;
|
|||
*
|
||||
* This shit is great for modders but it's pretty elaborate for how much it'll actually be used, lolol. -Eric
|
||||
*/
|
||||
@:nullSafety
|
||||
class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass implements IRegistryEntry<ConversationData>
|
||||
{
|
||||
/**
|
||||
|
@ -44,8 +40,9 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
|
|||
|
||||
/**
|
||||
* Conversation data as parsed from the JSON file.
|
||||
* `null` if the data could not be parsed or loaded.
|
||||
*/
|
||||
public final _data:ConversationData;
|
||||
public final _data:Null<ConversationData>;
|
||||
|
||||
/**
|
||||
* The current entry in the dialogue.
|
||||
|
@ -56,7 +53,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
|
|||
|
||||
function get_currentDialogueEntryCount():Int
|
||||
{
|
||||
return _data.dialogue.length;
|
||||
return _data?.dialogue?.length ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -68,12 +65,12 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
|
|||
|
||||
function get_currentDialogueLineCount():Int
|
||||
{
|
||||
return currentDialogueEntryData.text.length;
|
||||
return currentDialogueEntryData?.text?.length ?? 0;
|
||||
}
|
||||
|
||||
var currentDialogueEntryData(get, never):DialogueEntryData;
|
||||
var currentDialogueEntryData(get, never):Null<DialogueEntryData>;
|
||||
|
||||
function get_currentDialogueEntryData():DialogueEntryData
|
||||
function get_currentDialogueEntryData():Null<DialogueEntryData>
|
||||
{
|
||||
if (_data == null || _data.dialogue == null) return null;
|
||||
if (currentDialogueEntry < 0 || currentDialogueEntry >= _data.dialogue.length) return null;
|
||||
|
@ -85,22 +82,23 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
|
|||
|
||||
function get_currentDialogueLineString():String
|
||||
{
|
||||
return currentDialogueEntryData?.text[currentDialogueLine];
|
||||
// TODO: Replace "" with some placeholder text?
|
||||
return currentDialogueEntryData?.text[currentDialogueLine] ?? "";
|
||||
}
|
||||
|
||||
/**
|
||||
* AUDIO
|
||||
*/
|
||||
var music:FunkinSound;
|
||||
var music:Null<FunkinSound>;
|
||||
|
||||
/**
|
||||
* GRAPHICS
|
||||
*/
|
||||
var backdrop:FunkinSprite;
|
||||
var backdrop:Null<FunkinSprite>;
|
||||
|
||||
var currentSpeaker:Speaker;
|
||||
var currentSpeaker:Null<Speaker>;
|
||||
|
||||
var currentDialogueBox:DialogueBox;
|
||||
var currentDialogueBox:Null<DialogueBox>;
|
||||
|
||||
public function new(id:String)
|
||||
{
|
||||
|
@ -128,17 +126,23 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
|
|||
|
||||
function setupMusic():Void
|
||||
{
|
||||
if (_data.music == null) return;
|
||||
if (_data == null) return;
|
||||
|
||||
if (_data.music == null || (_data.music.asset ?? "") == "") return;
|
||||
|
||||
music = FunkinSound.load(Paths.music(_data.music.asset), 0.0, true, true, true);
|
||||
var fadeTime:Float = _data.music.fadeTime ?? 0.0;
|
||||
|
||||
if (_data.music.fadeTime > 0.0)
|
||||
if (fadeTime > 0.0)
|
||||
{
|
||||
FlxTween.tween(music, {volume: 1.0}, _data.music.fadeTime, {ease: FlxEase.linear});
|
||||
FlxTween.tween(music, {volume: 1.0}, fadeTime, {ease: FlxEase.linear});
|
||||
}
|
||||
else
|
||||
{
|
||||
music.volume = 1.0;
|
||||
if (music != null)
|
||||
{
|
||||
music.volume = 1.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -160,6 +164,8 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
|
|||
|
||||
function setupBackdrop():Void
|
||||
{
|
||||
if (_data == null) return;
|
||||
|
||||
if (backdrop != null)
|
||||
{
|
||||
backdrop.destroy();
|
||||
|
@ -175,12 +181,13 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
|
|||
switch (_data.backdrop)
|
||||
{
|
||||
case SOLID(backdropData):
|
||||
var targetColor:FlxColor = FlxColor.fromString(backdropData.color);
|
||||
var targetColor:Null<FlxColor> = FlxColor.fromString(backdropData.color);
|
||||
backdrop.makeSolidColor(Std.int(FlxG.width), Std.int(FlxG.height), targetColor);
|
||||
if (backdropData.fadeTime > 0.0)
|
||||
var fadeTime = backdropData.fadeTime ?? 0.0;
|
||||
if (fadeTime > 0.0)
|
||||
{
|
||||
backdrop.alpha = 0.0;
|
||||
FlxTween.tween(backdrop, {alpha: 1.0}, backdropData.fadeTime, {ease: EaseUtil.stepped(10)});
|
||||
FlxTween.tween(backdrop, {alpha: 1.0}, fadeTime, {ease: EaseUtil.stepped(10)});
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -204,7 +211,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
|
|||
|
||||
function showCurrentSpeaker():Void
|
||||
{
|
||||
var nextSpeakerId:String = currentDialogueEntryData.speaker;
|
||||
var nextSpeakerId:String = currentDialogueEntryData?.speaker ?? "";
|
||||
|
||||
// Skip the next steps if the current speaker is already displayed.
|
||||
if ((currentSpeaker != null && currentSpeaker.alive) && nextSpeakerId == currentSpeaker.id) return;
|
||||
|
@ -216,7 +223,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
|
|||
currentSpeaker = null;
|
||||
}
|
||||
|
||||
var nextSpeaker:Speaker = SpeakerRegistry.instance.fetchEntry(nextSpeakerId);
|
||||
var nextSpeaker:Null<Speaker> = SpeakerRegistry.instance.fetchEntry(nextSpeakerId);
|
||||
|
||||
if (nextSpeaker == null)
|
||||
{
|
||||
|
@ -242,11 +249,11 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
|
|||
|
||||
function playSpeakerAnimation():Void
|
||||
{
|
||||
var nextSpeakerAnimation:String = currentDialogueEntryData.speakerAnimation;
|
||||
var nextSpeakerAnimation:Null<String> = currentDialogueEntryData?.speakerAnimation;
|
||||
|
||||
if (nextSpeakerAnimation == null) return;
|
||||
|
||||
currentSpeaker.playAnimation(nextSpeakerAnimation);
|
||||
if (currentSpeaker != null) currentSpeaker.playAnimation(nextSpeakerAnimation);
|
||||
}
|
||||
|
||||
public function refresh():Void
|
||||
|
@ -256,7 +263,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
|
|||
|
||||
function showCurrentDialogueBox():Void
|
||||
{
|
||||
var nextDialogueBoxId:String = currentDialogueEntryData?.box;
|
||||
var nextDialogueBoxId:String = currentDialogueEntryData?.box ?? "";
|
||||
|
||||
// Skip the next steps if the current dialogue box is already displayed.
|
||||
if ((currentDialogueBox != null && currentDialogueBox.alive) && nextDialogueBoxId == currentDialogueBox.id) return;
|
||||
|
@ -268,7 +275,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
|
|||
currentDialogueBox = null;
|
||||
}
|
||||
|
||||
var nextDialogueBox:DialogueBox = DialogueBoxRegistry.instance.fetchEntry(nextDialogueBoxId);
|
||||
var nextDialogueBox:Null<DialogueBox> = DialogueBoxRegistry.instance.fetchEntry(nextDialogueBoxId);
|
||||
|
||||
if (nextDialogueBox == null)
|
||||
{
|
||||
|
@ -290,11 +297,11 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
|
|||
|
||||
function playDialogueBoxAnimation():Void
|
||||
{
|
||||
var nextDialogueBoxAnimation:String = currentDialogueEntryData?.boxAnimation;
|
||||
var nextDialogueBoxAnimation:Null<String> = currentDialogueEntryData?.boxAnimation;
|
||||
|
||||
if (nextDialogueBoxAnimation == null) return;
|
||||
|
||||
currentDialogueBox.playAnimation(nextDialogueBoxAnimation);
|
||||
if (currentDialogueBox != null) currentDialogueBox.playAnimation(nextDialogueBoxAnimation);
|
||||
}
|
||||
|
||||
function onTypingComplete():Void
|
||||
|
@ -363,20 +370,32 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
|
|||
}
|
||||
outroTween = null;
|
||||
|
||||
if (this.music != null) this.music.stop();
|
||||
this.music = null;
|
||||
if (this.music != null)
|
||||
{
|
||||
this.music.stop();
|
||||
this.music = null;
|
||||
}
|
||||
|
||||
if (currentSpeaker != null) currentSpeaker.kill();
|
||||
remove(currentSpeaker);
|
||||
currentSpeaker = null;
|
||||
if (currentSpeaker != null)
|
||||
{
|
||||
currentSpeaker.kill();
|
||||
remove(currentSpeaker);
|
||||
currentSpeaker = null;
|
||||
}
|
||||
|
||||
if (currentDialogueBox != null) currentDialogueBox.kill();
|
||||
remove(currentDialogueBox);
|
||||
currentDialogueBox = null;
|
||||
if (currentDialogueBox != null)
|
||||
{
|
||||
currentDialogueBox.kill();
|
||||
remove(currentDialogueBox);
|
||||
currentDialogueBox = null;
|
||||
}
|
||||
|
||||
if (backdrop != null) backdrop.destroy();
|
||||
remove(backdrop);
|
||||
backdrop = null;
|
||||
if (backdrop != null)
|
||||
{
|
||||
backdrop.destroy();
|
||||
remove(backdrop);
|
||||
backdrop = null;
|
||||
}
|
||||
|
||||
startConversation();
|
||||
}
|
||||
|
@ -392,7 +411,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
|
|||
dispatchEvent(new DialogueScriptEvent(DIALOGUE_SKIP, this, true));
|
||||
}
|
||||
|
||||
var outroTween:FlxTween;
|
||||
var outroTween:Null<FlxTween> = null;
|
||||
|
||||
public function startOutro():Void
|
||||
{
|
||||
|
@ -407,7 +426,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
|
|||
ease: EaseUtil.stepped(8)
|
||||
});
|
||||
|
||||
FlxTween.tween(this.music, {volume: 0.0}, outroData.fadeTime);
|
||||
if (this.music != null) FlxTween.tween(this.music, {volume: 0.0}, outroData.fadeTime);
|
||||
case NONE(_):
|
||||
// Immediately clean up.
|
||||
endOutro();
|
||||
|
@ -417,7 +436,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
|
|||
}
|
||||
}
|
||||
|
||||
public var completeCallback:() -> Void;
|
||||
public var completeCallback:Null<Void->Void> = null;
|
||||
|
||||
public function endOutro():Void
|
||||
{
|
||||
|
@ -477,7 +496,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
|
|||
{
|
||||
// Continue the dialog with more lines.
|
||||
state = Speaking;
|
||||
currentDialogueBox.appendText(currentDialogueLineString);
|
||||
if (currentDialogueBox != null) currentDialogueBox.appendText(currentDialogueLineString);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -489,7 +508,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
|
|||
propagateEvent(event);
|
||||
if (event.eventCanceled) return;
|
||||
|
||||
currentDialogueBox.skip();
|
||||
if (currentDialogueBox != null) currentDialogueBox.skip();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -564,17 +583,26 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl
|
|||
if (this.music != null) this.music.stop();
|
||||
this.music = null;
|
||||
|
||||
if (currentSpeaker != null) currentSpeaker.kill();
|
||||
remove(currentSpeaker);
|
||||
currentSpeaker = null;
|
||||
if (currentSpeaker != null)
|
||||
{
|
||||
currentSpeaker.kill();
|
||||
remove(currentSpeaker);
|
||||
currentSpeaker = null;
|
||||
}
|
||||
|
||||
if (currentDialogueBox != null) currentDialogueBox.kill();
|
||||
remove(currentDialogueBox);
|
||||
currentDialogueBox = null;
|
||||
if (currentDialogueBox != null)
|
||||
{
|
||||
currentDialogueBox.kill();
|
||||
remove(currentDialogueBox);
|
||||
currentDialogueBox = null;
|
||||
}
|
||||
|
||||
if (backdrop != null) backdrop.destroy();
|
||||
remove(backdrop);
|
||||
backdrop = null;
|
||||
if (backdrop != null)
|
||||
{
|
||||
backdrop.destroy();
|
||||
remove(backdrop);
|
||||
backdrop = null;
|
||||
}
|
||||
|
||||
this.clear();
|
||||
|
||||
|
|
|
@ -2,10 +2,8 @@ package funkin.play.event;
|
|||
|
||||
import flixel.tweens.FlxEase;
|
||||
// Data from the chart
|
||||
import funkin.data.song.SongData;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
// Data from the event schema
|
||||
import funkin.play.event.SongEvent;
|
||||
import funkin.data.event.SongEventSchema;
|
||||
import funkin.data.event.SongEventSchema.SongEventFieldType;
|
||||
|
||||
|
|
|
@ -3,10 +3,8 @@ package funkin.play.event;
|
|||
import flixel.FlxSprite;
|
||||
import funkin.play.character.BaseCharacter;
|
||||
// Data from the chart
|
||||
import funkin.data.song.SongData;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
// Data from the event schema
|
||||
import funkin.play.event.SongEvent;
|
||||
import funkin.data.event.SongEventSchema;
|
||||
import funkin.data.event.SongEventSchema.SongEventFieldType;
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package funkin.play.event;
|
||||
|
||||
import funkin.play.song.Song;
|
||||
import polymod.hscript.HScriptedClass;
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
package funkin.play.event;
|
||||
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.FlxCamera;
|
||||
import flixel.tweens.FlxEase;
|
||||
// Data from the chart
|
||||
import funkin.data.song.SongData;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
// Data from the event schema
|
||||
import funkin.play.event.SongEvent;
|
||||
import funkin.data.event.SongEventSchema;
|
||||
import funkin.data.event.SongEventSchema.SongEventFieldType;
|
||||
|
||||
|
|
|
@ -1,13 +1,8 @@
|
|||
package funkin.play.event;
|
||||
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.FlxCamera;
|
||||
import flixel.tweens.FlxEase;
|
||||
// Data from the chart
|
||||
import funkin.data.song.SongData;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
// Data from the event schema
|
||||
import funkin.play.event.SongEvent;
|
||||
import funkin.data.event.SongEventSchema;
|
||||
import funkin.data.event.SongEventSchema.SongEventFieldType;
|
||||
|
||||
|
|
3
source/funkin/play/event/SetCharacterSongEvent.hx
Normal file
3
source/funkin/play/event/SetCharacterSongEvent.hx
Normal file
|
@ -0,0 +1,3 @@
|
|||
package funkin.play.event;
|
||||
|
||||
// TODO: Add a song event which switches characters.
|
121
source/funkin/play/event/SetHealthIconSongEvent.hx
Normal file
121
source/funkin/play/event/SetHealthIconSongEvent.hx
Normal file
|
@ -0,0 +1,121 @@
|
|||
package funkin.play.event;
|
||||
|
||||
import funkin.data.event.SongEventSchema;
|
||||
import funkin.play.character.CharacterData.HealthIconData;
|
||||
import funkin.data.song.SongData;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
|
||||
/**
|
||||
* This class represents a handler for scroll speed events.
|
||||
*
|
||||
* Example: Set the health icon of Boyfriend to "bf-pixel":
|
||||
* ```
|
||||
* {
|
||||
* 'e': 'SetHealthIcon',
|
||||
* "v": {
|
||||
* "char": 0,
|
||||
* "id": "bf-pixel",
|
||||
*
|
||||
* // Optional params:
|
||||
* "scale": 1.0,
|
||||
* "flipX": false,
|
||||
* "isPixel": false,
|
||||
* "offsetX": 0.0,
|
||||
* "offsetY": 0.0
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
class SetHealthIconSongEvent extends SongEvent
|
||||
{
|
||||
public function new()
|
||||
{
|
||||
super('SetHealthIcon');
|
||||
}
|
||||
|
||||
public override function handleEvent(data:SongEventData):Void
|
||||
{
|
||||
// Does nothing if there is no PlayState.
|
||||
if (PlayState.instance == null) return;
|
||||
|
||||
// Works even if we are minimal mode.
|
||||
// if (PlayState.instance.isMinimalMode) return;
|
||||
|
||||
var offsets:Array<Float> = [data.value.offsetX ?? 0.0, data.value.offsetY ?? 0.0];
|
||||
|
||||
var healthIconData:HealthIconData =
|
||||
{
|
||||
id: data.value.id ?? "bf",
|
||||
scale: data.value.scale ?? 1.0,
|
||||
flipX: data.value.flipX ?? false,
|
||||
isPixel: data.value.isPixel ?? false,
|
||||
offsets: offsets,
|
||||
};
|
||||
|
||||
switch (data?.value?.char ?? 0)
|
||||
{
|
||||
case 0:
|
||||
trace('Applying Player health icon via song event: ${healthIconData.id}');
|
||||
PlayState.instance.iconP1.configure(healthIconData);
|
||||
case 1:
|
||||
trace('Applying Opponent health icon via song event: ${healthIconData.id}');
|
||||
PlayState.instance.iconP2.configure(healthIconData);
|
||||
default:
|
||||
trace('[WARN] Unknown character index: ' + data.value.char);
|
||||
}
|
||||
}
|
||||
|
||||
public override function getTitle():String
|
||||
{
|
||||
return 'Set Health Icon';
|
||||
}
|
||||
|
||||
public override function getEventSchema():SongEventSchema
|
||||
{
|
||||
return new SongEventSchema([
|
||||
{
|
||||
name: 'char',
|
||||
title: 'Character',
|
||||
defaultValue: 0,
|
||||
type: SongEventFieldType.ENUM,
|
||||
keys: ['Player' => 0, 'Opponent' => 1],
|
||||
},
|
||||
{
|
||||
name: 'id',
|
||||
title: 'Health Icon ID',
|
||||
defaultValue: 'bf',
|
||||
type: SongEventFieldType.STRING,
|
||||
},
|
||||
{
|
||||
name: 'scale',
|
||||
title: 'Scale',
|
||||
defaultValue: 1.0,
|
||||
type: SongEventFieldType.FLOAT,
|
||||
},
|
||||
{
|
||||
name: 'flipX',
|
||||
title: 'Flip X?',
|
||||
defaultValue: false,
|
||||
type: SongEventFieldType.BOOL,
|
||||
},
|
||||
{
|
||||
name: 'isPixel',
|
||||
title: 'Is Pixel?',
|
||||
defaultValue: false,
|
||||
type: SongEventFieldType.BOOL,
|
||||
},
|
||||
{
|
||||
name: 'offsetX',
|
||||
title: 'X Offset',
|
||||
defaultValue: 0,
|
||||
type: SongEventFieldType.FLOAT,
|
||||
},
|
||||
{
|
||||
name: 'offsetY',
|
||||
title: 'Y Offset',
|
||||
defaultValue: 0,
|
||||
type: SongEventFieldType.FLOAT,
|
||||
}
|
||||
]);
|
||||
}
|
||||
}
|
3
source/funkin/play/event/SetStageSongEvent.hx
Normal file
3
source/funkin/play/event/SetStageSongEvent.hx
Normal file
|
@ -0,0 +1,3 @@
|
|||
package funkin.play.event;
|
||||
|
||||
// TODO: Add a song event which switches stages.
|
|
@ -1,13 +1,9 @@
|
|||
package funkin.play.event;
|
||||
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.FlxCamera;
|
||||
import flixel.tweens.FlxEase;
|
||||
// Data from the chart
|
||||
import funkin.data.song.SongData;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
// Data from the event schema
|
||||
import funkin.play.event.SongEvent;
|
||||
import funkin.data.event.SongEventSchema;
|
||||
import funkin.data.event.SongEventSchema.SongEventFieldType;
|
||||
|
||||
|
|
|
@ -1,72 +1,40 @@
|
|||
package funkin.play.notes;
|
||||
|
||||
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
|
||||
import funkin.play.notes.NoteDirection;
|
||||
import flixel.graphics.frames.FlxFramesCollection;
|
||||
import funkin.util.assets.FlxAnimationUtil;
|
||||
import flixel.FlxG;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
import flixel.FlxSprite;
|
||||
import funkin.play.notes.notestyle.NoteStyle;
|
||||
|
||||
class NoteHoldCover extends FlxTypedSpriteGroup<FlxSprite>
|
||||
{
|
||||
static final FRAMERATE_DEFAULT:Int = 24;
|
||||
|
||||
static var glowFrames:FlxFramesCollection;
|
||||
|
||||
public var holdNote:SustainTrail;
|
||||
|
||||
var glow:FlxSprite;
|
||||
public var glow:FlxSprite;
|
||||
|
||||
var sparks:FlxSprite;
|
||||
|
||||
public function new()
|
||||
public function new(noteStyle:NoteStyle)
|
||||
{
|
||||
super(0, 0);
|
||||
|
||||
setup();
|
||||
}
|
||||
|
||||
public static function preloadFrames():Void
|
||||
{
|
||||
glowFrames = null;
|
||||
for (direction in Strumline.DIRECTIONS)
|
||||
{
|
||||
var directionName = direction.colorName.toTitleCase();
|
||||
|
||||
var atlas:FlxFramesCollection = Paths.getSparrowAtlas('holdCover${directionName}');
|
||||
atlas.parent.persist = true;
|
||||
|
||||
if (glowFrames != null)
|
||||
{
|
||||
glowFrames = FlxAnimationUtil.combineFramesCollections(glowFrames, atlas);
|
||||
}
|
||||
else
|
||||
{
|
||||
glowFrames = atlas;
|
||||
}
|
||||
}
|
||||
setupHoldNoteCover(noteStyle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add ALL the animations to this sprite. We will recycle and reuse the FlxSprite multiple times.
|
||||
*/
|
||||
function setup():Void
|
||||
function setupHoldNoteCover(noteStyle:NoteStyle):Void
|
||||
{
|
||||
glow = new FlxSprite();
|
||||
add(glow);
|
||||
if (glowFrames == null) preloadFrames();
|
||||
glow.frames = glowFrames;
|
||||
|
||||
for (direction in Strumline.DIRECTIONS)
|
||||
{
|
||||
var directionName = direction.colorName.toTitleCase();
|
||||
// TODO: null check here like how NoteSplash does
|
||||
noteStyle.buildHoldCoverSprite(this);
|
||||
|
||||
glow.animation.addByPrefix('holdCoverStart$directionName', 'holdCoverStart${directionName}0', FRAMERATE_DEFAULT, false, false, false);
|
||||
glow.animation.addByPrefix('holdCover$directionName', 'holdCover${directionName}0', FRAMERATE_DEFAULT, true, false, false);
|
||||
glow.animation.addByPrefix('holdCoverEnd$directionName', 'holdCoverEnd${directionName}0', FRAMERATE_DEFAULT, false, false, false);
|
||||
}
|
||||
|
||||
glow.animation.finishCallback = this.onAnimationFinished;
|
||||
glow.animation.onFinish.add(this.onAnimationFinished);
|
||||
|
||||
if (glow.animation.getAnimationList().length < 3 * 4)
|
||||
{
|
||||
|
|
|
@ -1,53 +1,32 @@
|
|||
package funkin.play.notes;
|
||||
|
||||
import funkin.play.notes.NoteDirection;
|
||||
import funkin.play.notes.notestyle.NoteStyle;
|
||||
import flixel.graphics.frames.FlxFramesCollection;
|
||||
import flixel.FlxG;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
import flixel.FlxSprite;
|
||||
|
||||
class NoteSplash extends FlxSprite
|
||||
{
|
||||
static final ALPHA:Float = 0.6;
|
||||
static final FRAMERATE_DEFAULT:Int = 24;
|
||||
static final FRAMERATE_VARIANCE:Int = 2;
|
||||
public var splashFramerate:Int = 24;
|
||||
public var splashFramerateVariance:Int = 2;
|
||||
|
||||
static var frameCollection:FlxFramesCollection;
|
||||
|
||||
public static function preloadFrames():Void
|
||||
{
|
||||
frameCollection = Paths.getSparrowAtlas('noteSplashes');
|
||||
frameCollection.parent.persist = true;
|
||||
}
|
||||
|
||||
public function new()
|
||||
public function new(noteStyle:NoteStyle)
|
||||
{
|
||||
super(0, 0);
|
||||
|
||||
setup();
|
||||
setupSplashGraphic(noteStyle);
|
||||
|
||||
this.alpha = ALPHA;
|
||||
this.animation.finishCallback = this.onAnimationFinished;
|
||||
this.animation.onFinish.add(this.onAnimationFinished);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add ALL the animations to this sprite. We will recycle and reuse the FlxSprite multiple times.
|
||||
*/
|
||||
function setup():Void
|
||||
function setupSplashGraphic(noteStyle:NoteStyle):Void
|
||||
{
|
||||
if (frameCollection?.parent?.isDestroyed ?? false) frameCollection = null;
|
||||
if (frameCollection == null) preloadFrames();
|
||||
|
||||
this.frames = frameCollection;
|
||||
|
||||
this.animation.addByPrefix('splash1Left', 'note impact 1 purple0', FRAMERATE_DEFAULT, false, false, false);
|
||||
this.animation.addByPrefix('splash1Down', 'note impact 1 blue0', FRAMERATE_DEFAULT, false, false, false);
|
||||
this.animation.addByPrefix('splash1Up', 'note impact 1 green0', FRAMERATE_DEFAULT, false, false, false);
|
||||
this.animation.addByPrefix('splash1Right', 'note impact 1 red0', FRAMERATE_DEFAULT, false, false, false);
|
||||
this.animation.addByPrefix('splash2Left', 'note impact 2 purple0', FRAMERATE_DEFAULT, false, false, false);
|
||||
this.animation.addByPrefix('splash2Down', 'note impact 2 blue0', FRAMERATE_DEFAULT, false, false, false);
|
||||
this.animation.addByPrefix('splash2Up', 'note impact 2 green0', FRAMERATE_DEFAULT, false, false, false);
|
||||
this.animation.addByPrefix('splash2Right', 'note impact 2 red0', FRAMERATE_DEFAULT, false, false, false);
|
||||
if (frames == null) noteStyle.buildSplashSprite(this);
|
||||
|
||||
if (this.animation.getAnimationList().length < 8)
|
||||
{
|
||||
|
@ -62,24 +41,21 @@ class NoteSplash extends FlxSprite
|
|||
|
||||
public function play(direction:NoteDirection, variant:Int = null):Void
|
||||
{
|
||||
if (variant == null) variant = FlxG.random.int(1, 2);
|
||||
|
||||
switch (direction)
|
||||
if (variant == null)
|
||||
{
|
||||
case NoteDirection.LEFT:
|
||||
this.playAnimation('splash${variant}Left');
|
||||
case NoteDirection.DOWN:
|
||||
this.playAnimation('splash${variant}Down');
|
||||
case NoteDirection.UP:
|
||||
this.playAnimation('splash${variant}Up');
|
||||
case NoteDirection.RIGHT:
|
||||
this.playAnimation('splash${variant}Right');
|
||||
var animationAmount:Int = this.animation.getAnimationList().filter(function(anim) return anim.name.startsWith('splash${direction.nameUpper}')).length
|
||||
- 1;
|
||||
variant = FlxG.random.int(0, animationAmount);
|
||||
}
|
||||
|
||||
// splashUP0, splashUP1, splashRIGHT0, etc.
|
||||
// the animations are processed via `NoteStyle.fetchSplashAnimationData()` in this format
|
||||
this.playAnimation('splash${direction.nameUpper}${variant}');
|
||||
|
||||
if (animation.curAnim == null) return;
|
||||
|
||||
// Vary the speed of the animation a bit.
|
||||
animation.curAnim.frameRate = FRAMERATE_DEFAULT + FlxG.random.int(-FRAMERATE_VARIANCE, FRAMERATE_VARIANCE);
|
||||
animation.curAnim.frameRate = splashFramerate + FlxG.random.int(-splashFramerateVariance, splashFramerateVariance);
|
||||
|
||||
// Center the animation on the note splash.
|
||||
offset.set(width * 0.3, height * 0.3);
|
||||
|
|
|
@ -3,8 +3,6 @@ package funkin.play.notes;
|
|||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongData.NoteParamData;
|
||||
import funkin.play.notes.notestyle.NoteStyle;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
import flixel.FlxSprite;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.graphics.shaders.HSVShader;
|
||||
|
||||
|
|
|
@ -8,14 +8,9 @@ import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
|
|||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.util.FlxSort;
|
||||
import funkin.play.notes.NoteHoldCover;
|
||||
import funkin.play.notes.NoteSplash;
|
||||
import funkin.play.notes.NoteSprite;
|
||||
import funkin.play.notes.SustainTrail;
|
||||
import funkin.graphics.FunkinSprite;
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.ui.options.PreferencesMenu;
|
||||
import funkin.util.SortUtil;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.play.notes.notekind.NoteKindManager;
|
||||
|
||||
/**
|
||||
|
@ -110,6 +105,8 @@ class Strumline extends FlxSpriteGroup
|
|||
|
||||
public var onNoteIncoming:FlxTypedSignal<NoteSprite->Void>;
|
||||
|
||||
var background:FunkinSprite;
|
||||
|
||||
var strumlineNotes:FlxTypedSpriteGroup<StrumlineNote>;
|
||||
var noteSplashes:FlxTypedSpriteGroup<NoteSplash>;
|
||||
var noteHoldCovers:FlxTypedSpriteGroup<NoteHoldCover>;
|
||||
|
@ -133,6 +130,8 @@ class Strumline extends FlxSpriteGroup
|
|||
|
||||
var heldKeys:Array<Bool> = [];
|
||||
|
||||
static final BACKGROUND_PAD:Int = 16;
|
||||
|
||||
public function new(noteStyle:NoteStyle, isPlayer:Bool)
|
||||
{
|
||||
super();
|
||||
|
@ -169,6 +168,13 @@ class Strumline extends FlxSpriteGroup
|
|||
this.noteSplashes.zIndex = 50;
|
||||
this.add(this.noteSplashes);
|
||||
|
||||
this.background = new FunkinSprite(0, 0).makeSolidColor(Std.int(this.width + BACKGROUND_PAD * 2), FlxG.height, 0xFF000000);
|
||||
// Convert the percent to a number between 0 and 1.
|
||||
this.background.alpha = Preferences.strumlineBackgroundOpacity / 100.0;
|
||||
this.background.scrollFactor.set(0, 0);
|
||||
this.background.x = -BACKGROUND_PAD;
|
||||
this.add(this.background);
|
||||
|
||||
this.refresh();
|
||||
|
||||
this.onNoteIncoming = new FlxTypedSignal<NoteSprite->Void>();
|
||||
|
@ -193,6 +199,25 @@ class Strumline extends FlxSpriteGroup
|
|||
this.active = true;
|
||||
}
|
||||
|
||||
override function set_y(value:Float):Float
|
||||
{
|
||||
super.set_y(value);
|
||||
|
||||
// Keep the background on the screen.
|
||||
if (this.background != null) this.background.y = 0;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
override function set_alpha(value:Float):Float
|
||||
{
|
||||
super.set_alpha(value);
|
||||
|
||||
this.background.alpha = Preferences.strumlineBackgroundOpacity / 100.0 * alpha;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public function refresh():Void
|
||||
{
|
||||
sort(SortUtil.byZIndex, FlxSort.ASCENDING);
|
||||
|
@ -754,9 +779,11 @@ class Strumline extends FlxSpriteGroup
|
|||
splash.x = this.x;
|
||||
splash.x += getXPos(direction);
|
||||
splash.x += INITIAL_OFFSET;
|
||||
splash.x += noteStyle.getSplashOffsets()[0] * splash.scale.x;
|
||||
|
||||
splash.y = this.y;
|
||||
splash.y -= INITIAL_OFFSET;
|
||||
splash.y += 0;
|
||||
splash.y += noteStyle.getSplashOffsets()[1] * splash.scale.y;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -779,12 +806,14 @@ class Strumline extends FlxSpriteGroup
|
|||
cover.x += getXPos(holdNote.noteDirection);
|
||||
cover.x += STRUMLINE_SIZE / 2;
|
||||
cover.x -= cover.width / 2;
|
||||
cover.x += -12; // Manual tweaking because fuck.
|
||||
cover.x += noteStyle.getHoldCoverOffsets()[0] * cover.scale.x;
|
||||
cover.x += -12; // hardcoded adjustment, because we are evil.
|
||||
|
||||
cover.y = this.y;
|
||||
cover.y += INITIAL_OFFSET;
|
||||
cover.y += STRUMLINE_SIZE / 2;
|
||||
cover.y += -96; // Manual tweaking because fuck.
|
||||
cover.y += noteStyle.getHoldCoverOffsets()[1] * cover.scale.y;
|
||||
cover.y += -96; // hardcoded adjustment, because we are evil.
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -817,7 +846,10 @@ class Strumline extends FlxSpriteGroup
|
|||
|
||||
if (holdNoteSprite != null)
|
||||
{
|
||||
var noteKindStyle:NoteStyle = NoteKindManager.getNoteStyle(note.kind, this.noteStyle.id) ?? this.noteStyle;
|
||||
var noteKindStyle:NoteStyle = NoteKindManager.getNoteStyle(note.kind, this.noteStyle.id);
|
||||
if (noteKindStyle == null) noteKindStyle = NoteKindManager.getNoteStyle(note.kind, null);
|
||||
if (noteKindStyle == null) noteKindStyle = this.noteStyle;
|
||||
|
||||
holdNoteSprite.setupHoldNoteGraphic(noteKindStyle);
|
||||
|
||||
holdNoteSprite.parentStrumline = this;
|
||||
|
@ -852,7 +884,7 @@ class Strumline extends FlxSpriteGroup
|
|||
if (noteSplashes.length < noteSplashes.maxSize)
|
||||
{
|
||||
// Create a new note splash.
|
||||
result = new NoteSplash();
|
||||
result = new NoteSplash(noteStyle);
|
||||
this.noteSplashes.add(result);
|
||||
}
|
||||
else
|
||||
|
@ -886,7 +918,7 @@ class Strumline extends FlxSpriteGroup
|
|||
if (noteHoldCovers.length < noteHoldCovers.maxSize)
|
||||
{
|
||||
// Create a new note hold cover.
|
||||
result = new NoteHoldCover();
|
||||
result = new NoteHoldCover(noteStyle);
|
||||
this.noteHoldCovers.add(result);
|
||||
}
|
||||
else
|
||||
|
@ -1010,4 +1042,42 @@ class Strumline extends FlxSpriteGroup
|
|||
{
|
||||
return FlxSort.byValues(order, a?.strumTime, b?.strumTime);
|
||||
}
|
||||
|
||||
override function findMinYHelper()
|
||||
{
|
||||
var value = Math.POSITIVE_INFINITY;
|
||||
for (member in group.members)
|
||||
{
|
||||
if (member == null) continue;
|
||||
// SKIP THE BACKGROUND
|
||||
if (member == this.background) continue;
|
||||
|
||||
var minY:Float;
|
||||
if (member.flixelType == SPRITEGROUP) minY = (cast member : FlxSpriteGroup).findMinY();
|
||||
else
|
||||
minY = member.y;
|
||||
|
||||
if (minY < value) value = minY;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
override function findMaxYHelper()
|
||||
{
|
||||
var value = Math.NEGATIVE_INFINITY;
|
||||
for (member in group.members)
|
||||
{
|
||||
if (member == null) continue;
|
||||
// SKIP THE BACKGROUND
|
||||
if (member == this.background) continue;
|
||||
|
||||
var maxY:Float;
|
||||
if (member.flixelType == SPRITEGROUP) maxY = (cast member : FlxSpriteGroup).findMaxY();
|
||||
else
|
||||
maxY = member.y + member.height;
|
||||
|
||||
if (maxY > value) value = maxY;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
package funkin.play.notes;
|
||||
|
||||
import funkin.play.notes.notestyle.NoteStyle;
|
||||
import funkin.play.notes.NoteDirection;
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import flixel.util.FlxDirectionFlags;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.graphics.FlxGraphic;
|
||||
import flixel.graphics.tile.FlxDrawTrianglesItem;
|
||||
import flixel.graphics.tile.FlxDrawTrianglesItem.DrawData;
|
||||
import flixel.math.FlxMath;
|
||||
import funkin.ui.options.PreferencesMenu;
|
||||
|
||||
/**
|
||||
* This is based heavily on the `FlxStrip` class. It uses `drawTriangles()` to clip a sustain note
|
||||
|
|
|
@ -2,7 +2,6 @@ package funkin.play.notes.notekind;
|
|||
|
||||
import funkin.modding.IScriptedClass.INoteScriptedClass;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import flixel.math.FlxMath;
|
||||
|
||||
/**
|
||||
* Class for note scripts
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue