Compare commits
58 Commits
v0.17.20
...
d38ff06262
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d38ff06262 | ||
|
|
45a4b10d11 | ||
|
|
fe12f397cc | ||
|
|
801f67b194 | ||
|
|
e331c8f446 | ||
|
|
51897584a7 | ||
|
|
1cf3d101aa | ||
|
|
1830e849d8 | ||
|
|
e3f246aa00 | ||
|
|
1eb679cf24 | ||
|
|
1963dc9fb9 | ||
|
|
396293c8af | ||
|
|
fbf6e910a3 | ||
|
|
9e4e861c32 | ||
|
|
d0b99a16c9 | ||
|
|
347e81c11c | ||
|
|
6b2a376dfe | ||
|
|
51e0ab7c48 | ||
|
|
87fd26ab89 | ||
|
|
0b1cee99bd | ||
|
|
4768df6867 | ||
|
|
423d502aa4 | ||
|
|
a74e9033b3 | ||
|
|
dc6b7de15f | ||
|
|
74f67d0216 | ||
|
|
d47f073d53 | ||
|
|
7407911914 | ||
|
|
9672a4fe57 | ||
|
|
6c6ff26586 | ||
|
|
ca6f701084 | ||
|
|
5793b7ca38 | ||
|
|
dee6ba95f2 | ||
|
|
67aef1dbe3 | ||
|
|
90f41a20a3 | ||
|
|
5f5dd48ee8 | ||
|
|
a8e0574f44 | ||
|
|
40f148b6b3 | ||
|
|
0eb37666f8 | ||
|
|
b73a653332 | ||
|
|
49f1b747b2 | ||
|
|
2c75f0bc9c | ||
|
|
97e17edfae | ||
|
|
f7e4e47466 | ||
|
|
8779abef9a | ||
|
|
4d1caa7fe0 | ||
|
|
78f3c31db1 | ||
|
|
3c852f17a0 | ||
|
|
8737a00b44 | ||
|
|
fff2c49f2e | ||
|
|
35c19a4d50 | ||
|
|
137e707c63 | ||
|
|
d2e9439e38 | ||
|
|
39cd7e4c1a | ||
|
|
57f2b9541d | ||
|
|
9d4065a05a | ||
|
|
424a3b3bee | ||
|
|
de3e1fcdc7 | ||
|
|
59f390de93 |
16
.github/500stars/README.md
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
# Techmino - 500-star Banner
|
||||
Created by NOT_A_ROBOT
|
||||
13 September, 2024
|
||||
|
||||
**Don't forget to attribute me when using this.**
|
||||
The image already includes sufficient attribution, so if you just don't crop that out, you shouldn't need to explicitly mention them.
|
||||
|
||||
## Attribution
|
||||
Created by NOT_A_ROBOT
|
||||
GitHub logo (on Z-character's screen) by GitHub
|
||||
Background (space stars) originally by MrZ, ported to JS by NOT_A_ROBOT for rendering
|
||||
Block skin (featured in the background) by Scf, slightly modified to make it darker
|
||||
Z-character drawn by 葉枭, designed by MrZ
|
||||
Techmino by MrZ and many contributors
|
||||
|
||||
Techmino is fun! https://github.com/26F-Studio/Techmino
|
||||
BIN
.github/500stars/exported.png
vendored
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
.github/build/extraLibs/Windows_x64/discord-rpc.dll
vendored
Normal file
BIN
.github/build/extraLibs/Windows_x86/discord-rpc.dll
vendored
Normal file
BIN
.github/donate/donate.png
vendored
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
32
.github/workflows/main.yml
vendored
@@ -130,7 +130,7 @@ jobs:
|
||||
prerelease: ${{ startsWith(github.ref, 'refs/tags/pre') }}
|
||||
|
||||
auto-test:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
needs: build-core
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -141,6 +141,25 @@ jobs:
|
||||
with:
|
||||
font-path: ./parts/fonts/proportional.otf
|
||||
language-folder: ./parts/language
|
||||
- name: Download core love package
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: ${{ env.CORE_LOVE_ARTIFACT_NAME }}
|
||||
- name: Download love
|
||||
shell: bash
|
||||
run: |
|
||||
curl -OL --retry 5 https://github.com/love2d/love/releases/download/11.4/love-11.4-x86_64.AppImage
|
||||
chmod +x love-11.4-x86_64.AppImage
|
||||
- name: Prepare PulseAudio and AppImage
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install pulseaudio pulseaudio-utils pavucontrol alsa-oss alsa-utils libfuse2 -y
|
||||
- name: Run automated test
|
||||
uses: coactions/setup-xvfb@v1
|
||||
with:
|
||||
run: |
|
||||
./love-11.4-x86_64.AppImage ${{ env.CORE_LOVE_PACKAGE_PATH }} --test
|
||||
|
||||
build-android:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -331,6 +350,12 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
rm ./ColdClear/universal/libcold_clear.a
|
||||
- name: Use Xcode 15.3
|
||||
# Xcode 15.4 segfaults
|
||||
# see https://forums.developer.apple.com/forums/thread/757398
|
||||
uses: mobiledevops/xcode-select-version-action@v1
|
||||
with:
|
||||
xcode-select-version: 15.3
|
||||
- name: Build macOS packages
|
||||
id: build-packages
|
||||
uses: love-actions/love-actions-macos-portable@v1
|
||||
@@ -339,6 +364,7 @@ jobs:
|
||||
bundle-id: ${{ steps.process-app-name.outputs.bundle-id }}
|
||||
copyright: "Copyright © 2019-2023 26F-Studio. Some Rights Reserved."
|
||||
icon-path: ./.github/build/macOS/${{ env.BUILD_TYPE }}/icon.icns
|
||||
love-ref: "11.5"
|
||||
love-package: ${{ env.CORE_LOVE_PACKAGE_PATH }}
|
||||
libs-path: ./ColdClear/universal/
|
||||
product-name: ${{ steps.process-app-name.outputs.product-name }}
|
||||
@@ -469,8 +495,8 @@ jobs:
|
||||
icon-path: ./.github/build/windows/${{ env.BUILD_TYPE }}/icon.ico
|
||||
rc-path: ./.github/build/windows/${{ env.BUILD_TYPE }}/template.rc
|
||||
love-package: ${{ env.CORE_LOVE_PACKAGE_PATH }}
|
||||
extra-assets-x86: ./ColdClear/x86/CCloader.dll ./ColdClear/x86/cold_clear.dll
|
||||
extra-assets-x64: ./ColdClear/x64/CCloader.dll ./ColdClear/x64/cold_clear.dll
|
||||
extra-assets-x86: ./ColdClear/x86/CCloader.dll ./ColdClear/x86/cold_clear.dll ./.github/build/extraLibs/Windows_x64/discord-rpc.dll
|
||||
extra-assets-x64: ./ColdClear/x64/CCloader.dll ./ColdClear/x64/cold_clear.dll ./.github/build/extraLibs/Windows_x86/discord-rpc.dll
|
||||
product-name: ${{ steps.process-app-name.outputs.product-name }}
|
||||
app-id: ${{ secrets.WINDOWS_APP_ID }}
|
||||
project-website: https://www.studio26f.org/
|
||||
|
||||
1
.gitignore
vendored
@@ -1,5 +1,4 @@
|
||||
.vscode
|
||||
libAndroid
|
||||
*.ini
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
@@ -8,8 +8,6 @@ local path=''
|
||||
|
||||
local type=type
|
||||
local timer=love.timer.getTime
|
||||
local TRD=love.thread.newThread("\n")
|
||||
local TRD_isRunning=TRD.isRunning
|
||||
|
||||
local WS={}
|
||||
local wsList=setmetatable({},{
|
||||
@@ -151,7 +149,7 @@ function WS.update(dt)
|
||||
local time=timer()
|
||||
for name,ws in next,wsList do
|
||||
if ws.real and ws.status~='dead' then
|
||||
if TRD_isRunning(ws.thread) then
|
||||
if ws.thread:isRunning() then
|
||||
if ws.triggerCHN:getCount()==0 then
|
||||
ws.triggerCHN:push(0)
|
||||
end
|
||||
|
||||
@@ -15,6 +15,7 @@ do-- Connect
|
||||
|
||||
SOCK:settimeout(timeout)
|
||||
local res,err=SOCK:connect(host,port)
|
||||
-- print('C0',res,err)
|
||||
assert(res,err)
|
||||
|
||||
-- WebSocket handshake
|
||||
@@ -31,6 +32,7 @@ do-- Connect
|
||||
|
||||
-- First line of HTTP
|
||||
res,err=SOCK:receive('*l')
|
||||
-- print('C',res,err)
|
||||
assert(res,err)
|
||||
local code,ctLen
|
||||
code=res:find(' ')
|
||||
@@ -39,6 +41,7 @@ do-- Connect
|
||||
-- Get body length from headers and remove headers
|
||||
repeat
|
||||
res,err=SOCK:receive('*l')
|
||||
-- print('H',res,err)
|
||||
assert(res,err)
|
||||
if not ctLen and res:find('content-length') then
|
||||
ctLen=tonumber(res:match('%d+')) or 0
|
||||
@@ -53,6 +56,7 @@ do-- Connect
|
||||
-- Content(?)
|
||||
if ctLen then
|
||||
res,err=SOCK:receive(ctLen)
|
||||
-- print('R',res,err)
|
||||
if code~='101' then
|
||||
res=JSON.decode(assert(res,err))
|
||||
error((code or "XXX")..":"..(res and res.reason or "Server Error"))
|
||||
@@ -140,10 +144,10 @@ local readThread=coroutine.wrap(function()
|
||||
assert(res,err)
|
||||
length=shl(byte(res,1),8)+byte(res,2)
|
||||
elseif length==127 then
|
||||
local lenData
|
||||
lenData,err=_receive(SOCK,8)
|
||||
-- 'res' is 'lenData' here
|
||||
res,err=_receive(SOCK,8)
|
||||
assert(res,err)
|
||||
local _,_,_,_,_5,_6,_7,_8=byte(lenData,1,8)
|
||||
local _,_,_,_,_5,_6,_7,_8=byte(res,1,8)
|
||||
length=shl(_5,24)+shl(_6,16)+shl(_7,8)+_8
|
||||
end
|
||||
res,err=_receive(SOCK,length)
|
||||
@@ -162,12 +166,14 @@ local readThread=coroutine.wrap(function()
|
||||
lBuffer=lBuffer..res
|
||||
if fin then
|
||||
CHN_push(readCHN,lBuffer)
|
||||
-- print('M',lBuffer)
|
||||
lBuffer=""
|
||||
end
|
||||
else
|
||||
CHN_push(readCHN,op)
|
||||
if fin then
|
||||
CHN_push(readCHN,res)
|
||||
-- print('S',res)
|
||||
lBuffer=""
|
||||
else
|
||||
lBuffer=res
|
||||
|
||||
@@ -15,6 +15,7 @@ local timer=love.timer.getTime
|
||||
local next=next
|
||||
local floor,ceil=math.floor,math.ceil
|
||||
local max,min=math.max,math.min
|
||||
local match=string.match
|
||||
local sub,ins,rem=string.sub,table.insert,table.remove
|
||||
local xOy=SCR.xOy
|
||||
local FONT=FONT
|
||||
@@ -142,13 +143,21 @@ local button={
|
||||
type='button',
|
||||
mustHaveText=true,
|
||||
ATV=0,-- Activating time(0~8)
|
||||
textAlreadyWrapped=false,-- Text already wrapped? (Managed by :setObject, can be override, this will be true if obj has a '\n')
|
||||
}
|
||||
function button:reset()
|
||||
self.ATV=0
|
||||
end
|
||||
function button:setObject(obj)
|
||||
if type(obj)=='string' or type(obj)=='number' then
|
||||
self.obj=gc.newText(FONT.get(self.font,self.fType),obj)
|
||||
if match(obj,"\n") then
|
||||
self.textAlreadyWrapped=true
|
||||
self.obj=gc.newText(FONT.get(self.font,self.fType))
|
||||
self.obj:addf(obj,self.w-self.edge*2,(self.align=='L' and 'left') or (self.align=='R' and 'right') or 'center')
|
||||
else
|
||||
self.textAlreadyWrapped=false
|
||||
self.obj=gc.newText(FONT.get(self.font,self.fType),obj)
|
||||
end
|
||||
elseif obj then
|
||||
self.obj=obj
|
||||
end
|
||||
@@ -194,16 +203,7 @@ function button:draw()
|
||||
local ox,oy=obj:getWidth()*.5,obj:getHeight()*.5
|
||||
local y0=y+h*.5
|
||||
gc_setColor(1,1,1,.2+ATV*.05)
|
||||
if self.align=='M' then
|
||||
local x0=x+w*.5
|
||||
local kx=obj:type()=='Text' and min(w/ox/2,1) or 1
|
||||
gc_draw(obj,x0-1,y0-1,nil,kx,1,ox,oy)
|
||||
gc_draw(obj,x0-1,y0+1,nil,kx,1,ox,oy)
|
||||
gc_draw(obj,x0+1,y0-1,nil,kx,1,ox,oy)
|
||||
gc_draw(obj,x0+1,y0+1,nil,kx,1,ox,oy)
|
||||
gc_setColor(r*.55,g*.55,b*.55)
|
||||
gc_draw(obj,x0,y0,nil,kx,1,ox,oy)
|
||||
elseif self.align=='L' then
|
||||
if self.align=='L' or self.textAlreadyWrapped then
|
||||
local edge=self.edge
|
||||
gc_draw(obj,x+edge-1,y0-1-oy)
|
||||
gc_draw(obj,x+edge-1,y0+1-oy)
|
||||
@@ -219,6 +219,15 @@ function button:draw()
|
||||
gc_draw(obj,x0+1,y0+1-oy)
|
||||
gc_setColor(r*.55,g*.55,b*.55)
|
||||
gc_draw(obj,x0,y0-oy)
|
||||
else--if self.align=='M' then
|
||||
local x0=x+w*.5
|
||||
local kx=obj:type()=='Text' and min(w/ox/2,1) or 1
|
||||
gc_draw(obj,x0-1,y0-1,nil,kx,1,ox,oy)
|
||||
gc_draw(obj,x0-1,y0+1,nil,kx,1,ox,oy)
|
||||
gc_draw(obj,x0+1,y0-1,nil,kx,1,ox,oy)
|
||||
gc_draw(obj,x0+1,y0+1,nil,kx,1,ox,oy)
|
||||
gc_setColor(r*.55,g*.55,b*.55)
|
||||
gc_draw(obj,x0,y0,nil,kx,1,ox,oy)
|
||||
end
|
||||
end
|
||||
function button:getInfo()
|
||||
@@ -290,13 +299,21 @@ local key={
|
||||
type='key',
|
||||
mustHaveText=true,
|
||||
ATV=0,-- Activating time(0~4)
|
||||
textAlreadyWrapped=false,---See button.setObject (line 146)
|
||||
}
|
||||
function key:reset()
|
||||
self.ATV=0
|
||||
end
|
||||
function key:setObject(obj)
|
||||
if type(obj)=='string' or type(obj)=='number' then
|
||||
self.obj=gc.newText(FONT.get(self.font,self.fType),obj)
|
||||
if match(obj,"\n") then
|
||||
self.textAlreadyWrapped=true
|
||||
self.obj=gc.newText(FONT.get(self.font,self.fType))
|
||||
self.obj:addf(obj,self.w-self.edge*2,(self.align=='L' and 'left') or (self.align=='R' and 'right') or 'center')
|
||||
else
|
||||
self.textAlreadyWrapped=false
|
||||
self.obj=gc.newText(FONT.get(self.font,self.fType),obj)
|
||||
end
|
||||
elseif obj then
|
||||
self.obj=obj
|
||||
end
|
||||
@@ -354,14 +371,15 @@ function key:draw()
|
||||
-- Drawable
|
||||
local obj=self.obj
|
||||
local ox,oy=obj:getWidth()*.5,obj:getHeight()*.5
|
||||
|
||||
gc_setColor(r,g,b)
|
||||
if align=='M' then
|
||||
local kx=obj:type()=='Text' and min(w/ox/2,1) or 1
|
||||
gc_draw(obj,x+w*.5,y+h*.5,nil,kx,1,ox,oy)
|
||||
elseif align=='L' then
|
||||
gc_draw(obj,x+self.edge,y-oy+h*.5)
|
||||
if align=='L' or self.textAlreadyWrapped then
|
||||
gc_draw(obj,x+self.edge,y+h*.5-oy)
|
||||
elseif align=='R' then
|
||||
gc_draw(obj,x+w-self.edge-ox*2,y-oy+h*.5)
|
||||
else--if align=='M' then
|
||||
local kx=obj:type()=='Text' and min(w/ox/2,1) or 1
|
||||
gc_draw(obj,x+w*.5,y+h*.5,nil,kx,1,ox,oy)
|
||||
end
|
||||
end
|
||||
function key:getInfo()
|
||||
@@ -1382,10 +1400,13 @@ function WIDGET.setLang(widgetText)
|
||||
t=W.name or "##"
|
||||
W.color=COLOR.dV
|
||||
end
|
||||
if type(t)=='string' and W.font then
|
||||
t=gc.newText(FONT.get(W.font),t)
|
||||
if type(W.setObject)=='function' then
|
||||
W:setObject(t)
|
||||
elseif type(t)=='string' and W.font then
|
||||
W.obj=gc.newText(FONT.get(W.font or 30),t)
|
||||
else
|
||||
W.obj=t
|
||||
end
|
||||
W.obj=t
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -12,6 +12,8 @@ Lua is free software distributed under the terms of the MIT license. Copyright
|
||||
|
||||
json.lua is copyrighted by rxi. © 2022 rxi.
|
||||
|
||||
discord-rpc.dll is copyrighted by Discord, Inc. © 2017 Discord, Inc.
|
||||
|
||||
IBM Plex is copyrighted by the International Business Machines Corporation. IBM and IBM Plex are trademarks of IBM Corp, registered in many jurisdictions worldwide. IBM Plex is licensed under the SIL Open Font License, Version 1.1.
|
||||
|
||||
|
||||
|
||||
83
main.lua
@@ -294,39 +294,41 @@ IMG.init{
|
||||
},
|
||||
}
|
||||
SKIN.load{
|
||||
{name="crystal_scf", path='media/image/skin/crystal_scf.png'},
|
||||
{name="matte_mrz", path='media/image/skin/matte_mrz.png'},
|
||||
{name="shiny_chno", path='media/image/skin/shiny_chno.png'},
|
||||
{name="contrast_mrz", path='media/image/skin/contrast_mrz.png'},
|
||||
{name="polkadots_scf", path='media/image/skin/polkadots_scf.png'},
|
||||
{name="toy_scf", path='media/image/skin/toy_scf.png'},
|
||||
{name="smooth_mrz", path='media/image/skin/smooth_mrz.png'},
|
||||
{name="simple_scf", path='media/image/skin/simple_scf.png'},
|
||||
{name="glass_scf", path='media/image/skin/glass_scf.png'},
|
||||
{name="penta_scf", path='media/image/skin/penta_scf.png'},
|
||||
{name="bubble_scf", path='media/image/skin/bubble_scf.png'},
|
||||
{name="minoes_scf", path='media/image/skin/minoes_scf.png'},
|
||||
{name="pure_mrz", path='media/image/skin/pure_mrz.png'},
|
||||
{name="bright_scf", path='media/image/skin/bright_scf.png'},
|
||||
{name="glow_mrz", path='media/image/skin/glow_mrz.png'},
|
||||
{name="plastic_mrz", path='media/image/skin/plastic_mrz.png'},
|
||||
{name="paper_mrz", path='media/image/skin/paper_mrz.png'},
|
||||
{name="yinyang_scf", path='media/image/skin/yinyang_scf.png'},
|
||||
{name="cartooncup_earety", path='media/image/skin/cartooncup_earety.png'},
|
||||
{name="jelly_miya", path='media/image/skin/jelly_miya.png'},
|
||||
{name="guidetris_xmiao_lusisi",path='media/image/skin/guidetris_xmiao_lusisi.png'},
|
||||
{name="brick_notypey", path='media/image/skin/brick_notypey.png'},
|
||||
{name="gem_notypey", path='media/image/skin/gem_notypey.png'},
|
||||
{name="classic", path='media/image/skin/classic_unknown.png'},
|
||||
{name="ball_shaw", path='media/image/skin/ball_shaw.png'},
|
||||
{name="retro_notypey", path='media/image/skin/retro_notypey.png'},
|
||||
{name="pixel_chno", path='media/image/skin/pixel_chno.png'},
|
||||
{name="pastel_chno", path='media/image/skin/pastel_chno.png'},
|
||||
{name="letters_chno", path='media/image/skin/letters_chno.png'},
|
||||
{name="kanji_chno", path='media/image/skin/kanji_chno.png'},
|
||||
{name="textbone_mrz", path='media/image/skin/textbone_mrz.png'},
|
||||
{name="coloredbone_mrz", path='media/image/skin/coloredbone_mrz.png'},
|
||||
{name="wtf", path='media/image/skin/wtf_mrz.png'},
|
||||
{name="Arcade (Asriel)", path='media/image/skin/asriel/arcade.png'},
|
||||
{name="Cardboard (Asriel, slimenergy)", path='media/image/skin/asriel/cardboard.png'},
|
||||
{name="Crystal (Scf)", path='media/image/skin/scf/crystal.png'},
|
||||
{name="Matte (MrZ)", path='media/image/skin/mrz/matte.png'},
|
||||
{name="Shiny (CHNO)", path='media/image/skin/chno/shiny.png'},
|
||||
{name="Contrast (MrZ)", path='media/image/skin/mrz/contrast.png'},
|
||||
{name="Polkadots (Scf)", path='media/image/skin/scf/polkadots.png'},
|
||||
{name="Toy (Scf)", path='media/image/skin/scf/toy.png'},
|
||||
{name="Smooth (MrZ)", path='media/image/skin/mrz/smooth.png'},
|
||||
{name="Simple (Scf)", path='media/image/skin/scf/simple.png'},
|
||||
{name="Glass (Scf)", path='media/image/skin/scf/glass.png'},
|
||||
{name="Penta (Scf)", path='media/image/skin/scf/penta.png'},
|
||||
{name="Bubble (Scf)", path='media/image/skin/scf/bubble.png'},
|
||||
{name="Minoes (Scf)", path='media/image/skin/scf/minoes.png'},
|
||||
{name="pure (MrZ)", path='media/image/skin/mrz/pure.png'},
|
||||
{name="bright (Scf)", path='media/image/skin/scf/bright.png'},
|
||||
{name="Glow (MrZ)", path='media/image/skin/mrz/glow.png'},
|
||||
{name="Plastic (MrZ)", path='media/image/skin/mrz/plastic.png'},
|
||||
{name="paper (MrZ)", path='media/image/skin/mrz/paper.png'},
|
||||
{name="Yinyang (Scf)", path='media/image/skin/scf/yinyang.png'},
|
||||
{name="Cartooncup (Earety)", path='media/image/skin/earety/cartooncup.png'},
|
||||
{name="Jelly (Miya)", path='media/image/skin/miya/jelly.png'},
|
||||
{name="guidetris (xmiao, lusisi)",path='media/image/skin/guidetris_xmiao_lusisi.png'},
|
||||
{name="brick (Notypey)", path='media/image/skin/notypey/brick.png'},
|
||||
{name="Gem (Notypey)", path='media/image/skin/notypey/gem.png'},
|
||||
{name="Classic", path='media/image/skin/unknown/classic.png'},
|
||||
{name="Ball (Shaw)", path='media/image/skin/shaw/ball.png'},
|
||||
{name="Retro (Notypey)", path='media/image/skin/notypey/retro.png'},
|
||||
{name="Pixel (CHNO)", path='media/image/skin/chno/pixel.png'},
|
||||
{name="Pastel (CHNO)", path='media/image/skin/chno/pastel.png'},
|
||||
{name="Letters (CHNO)", path='media/image/skin/chno/letters.png'},
|
||||
{name="Kanji (CHNO)", path='media/image/skin/chno/kanji.png'},
|
||||
{name="Textbone (MrZ)", path='media/image/skin/mrz/textbone.png'},
|
||||
{name="Coloredbone (MrZ)", path='media/image/skin/mrz/coloredbone.png'},
|
||||
{name="WTF (MrZ)", path='media/image/skin/mrz/wtf.png'},
|
||||
}
|
||||
|
||||
-- Initialize sound libs
|
||||
@@ -425,7 +427,8 @@ do
|
||||
for _,v in next,SETTING.skin do if v<1 or v>17 then v=17 end end
|
||||
if not RSlist[SETTING.RS] then SETTING.RS='TRS' end
|
||||
if SETTING.ghostType=='greyCell' then SETTING.ghostType='grayCell' end
|
||||
if type(SETTING.skinSet)=='number' then SETTING.skinSet='crystal_scf' end
|
||||
if type(SETTING.skinSet)=='number' then SETTING.skinSet='Crystal (Scf)' end
|
||||
if string.find(SETTING.skinSet,"_") then SETTING.skinSet='Crystal (Scf)' end
|
||||
if not TABLE.find({8,10,13,17,22,29,37,47,62,80,100},SETTING.frameMul) then SETTING.frameMul=100 end
|
||||
if SETTING.cv then SETTING.vocPack,SETTING.cv=SETTING.cv end
|
||||
if type(SETTING.bg)~='string' then SETTING.bg='on' end
|
||||
@@ -592,17 +595,21 @@ end
|
||||
table.sort(REPLAY,function(a,b) return a.fileName>b.fileName end)
|
||||
|
||||
AUTHURL="https://www.studio26f.org/oauth?product=techmino"
|
||||
AUTHHOST="www.studio26f.org"
|
||||
WS.switchHost('www.studio26f.org','80','/techmino/ws/v1')
|
||||
HTTP.setHost("www.studio26f.org")
|
||||
AUTHHOST="www.studio26f.org:8080"
|
||||
WS.switchHost('www.studio26f.org','8081','/techmino/ws/v1')
|
||||
HTTP.setHost("www.studio26f.org:8081")
|
||||
HTTP.setThreadCount(1)
|
||||
|
||||
-- Discord RPC
|
||||
DiscordRPC=require'parts.discordRPC'
|
||||
DiscordRPC.update()
|
||||
|
||||
table.insert(_LOADTIMELIST_,("Load Resources: %.3fs"):format(TIME()-_LOADTIME_))
|
||||
|
||||
for i=1,#_LOADTIMELIST_ do LOG(_LOADTIMELIST_[i]) end
|
||||
|
||||
-- Launch testing task if launch param received
|
||||
if TABLE.find(arg,'-- test') then
|
||||
if TABLE.find(arg,'--test') then
|
||||
TASK.new(function()
|
||||
while not LOADED do coroutine.yield() end
|
||||
|
||||
|
||||
BIN
media/image/skin/asriel/arcade.png
Executable file
|
After Width: | Height: | Size: 20 KiB |
BIN
media/image/skin/asriel/cardboard.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 8.0 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 613 B After Width: | Height: | Size: 613 B |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 91 B After Width: | Height: | Size: 91 B |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 837 B After Width: | Height: | Size: 837 B |
|
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 8.8 KiB |
@@ -256,54 +256,34 @@ end
|
||||
large value will be encoded as 1xxxxxxx(high)-1xxxxxxx-...-0xxxxxxx(low)
|
||||
|
||||
Example (decoded):
|
||||
6,1, 20,-1, 0,2, 26,-2, 872,4, ...
|
||||
26,1, 42,-1, ...
|
||||
This means:
|
||||
Press key1 at 6f
|
||||
Release key1 at 26f (6+20)
|
||||
Press key2 at the same time (26+0)
|
||||
Release key 2 after 26 frame (26+26)
|
||||
Press key 4 after 872 frame (52+872)
|
||||
Press key1 at 26f
|
||||
Release key1 at 42f
|
||||
...
|
||||
]]
|
||||
function DATA.dumpRecording(list,ptr)
|
||||
local out=""
|
||||
local buffer=""
|
||||
if not ptr then ptr=1 end
|
||||
local prevFrm=list[ptr-2] or 0
|
||||
while list[ptr] do
|
||||
-- Flush buffer
|
||||
if #buffer>10 then
|
||||
if #buffer>26 then
|
||||
out=out..buffer
|
||||
buffer=""
|
||||
end
|
||||
|
||||
-- Encode time
|
||||
local t=list[ptr]-prevFrm
|
||||
prevFrm=list[ptr]
|
||||
buffer=buffer.._encode(t)
|
||||
|
||||
-- Encode event
|
||||
buffer=buffer.._encode(list[ptr+1])
|
||||
|
||||
-- Step
|
||||
ptr=ptr+2
|
||||
buffer=buffer.._encode(list[ptr])
|
||||
ptr=ptr+1
|
||||
end
|
||||
return out..buffer,ptr
|
||||
end
|
||||
function DATA.pumpRecording(str,L)
|
||||
local len=#str
|
||||
local p=1
|
||||
local data
|
||||
|
||||
local curFrm=L[#L-1] or 0
|
||||
while p<=len do
|
||||
local code,event
|
||||
-- Read delta time
|
||||
code,p=_decode(str,p)
|
||||
curFrm=curFrm+code
|
||||
ins(L,curFrm)
|
||||
|
||||
event,p=_decode(str,p)
|
||||
ins(L,event)
|
||||
data,p=_decode(str,p)
|
||||
ins(L,data)
|
||||
end
|
||||
end
|
||||
do-- function DATA.saveReplay()
|
||||
|
||||
327
parts/discordRPC.lua
Normal file
@@ -0,0 +1,327 @@
|
||||
local appId='1288557386700951554'
|
||||
|
||||
local ffi=require"ffi"
|
||||
|
||||
local RPC_C
|
||||
if SYSTEM=='Windows' then
|
||||
local suc
|
||||
suc,RPC_C=pcall(ffi.load,"discord-rpc")
|
||||
if not (suc and RPC_C) then
|
||||
print("Failed to load Discord-RPC lib",RPC_C)
|
||||
MES.new('error',"Failed to load Discord-RPC lib")
|
||||
RPC_C=nil
|
||||
end
|
||||
end
|
||||
|
||||
local RPC
|
||||
if RPC_C then
|
||||
RPC={}
|
||||
ffi.cdef[[
|
||||
typedef struct DiscordRichPresence {
|
||||
const char* state; /* max 128 bytes */
|
||||
const char* details; /* max 128 bytes */
|
||||
int64_t startTimestamp;
|
||||
int64_t endTimestamp;
|
||||
const char* largeImageKey; /* max 32 bytes */
|
||||
const char* largeImageText; /* max 128 bytes */
|
||||
const char* smallImageKey; /* max 32 bytes */
|
||||
const char* smallImageText; /* max 128 bytes */
|
||||
const char* partyId; /* max 128 bytes */
|
||||
int partySize;
|
||||
int partyMax;
|
||||
const char* matchSecret; /* max 128 bytes */
|
||||
const char* joinSecret; /* max 128 bytes */
|
||||
const char* spectateSecret; /* max 128 bytes */
|
||||
int8_t instance;
|
||||
} DiscordRichPresence;
|
||||
|
||||
typedef struct DiscordUser {
|
||||
const char* userId;
|
||||
const char* username;
|
||||
const char* discriminator;
|
||||
const char* avatar;
|
||||
} DiscordUser;
|
||||
|
||||
typedef void (*readyPtr)(const DiscordUser* request);
|
||||
typedef void (*disconnectedPtr)(int errorCode, const char* message);
|
||||
typedef void (*erroredPtr)(int errorCode, const char* message);
|
||||
typedef void (*joinGamePtr)(const char* joinSecret);
|
||||
typedef void (*spectateGamePtr)(const char* spectateSecret);
|
||||
typedef void (*joinRequestPtr)(const DiscordUser* request);
|
||||
|
||||
typedef struct DiscordEventHandlers {
|
||||
readyPtr ready;
|
||||
disconnectedPtr disconnected;
|
||||
erroredPtr errored;
|
||||
joinGamePtr joinGame;
|
||||
spectateGamePtr spectateGame;
|
||||
joinRequestPtr joinRequest;
|
||||
} DiscordEventHandlers;
|
||||
|
||||
void Discord_Initialize(const char* applicationId,
|
||||
DiscordEventHandlers* handlers,
|
||||
int autoRegister,
|
||||
const char* optionalSteamId);
|
||||
void Discord_Shutdown(void);
|
||||
void Discord_RunCallbacks(void);
|
||||
void Discord_UpdatePresence(const DiscordRichPresence* presence);
|
||||
void Discord_ClearPresence(void);
|
||||
void Discord_Respond(const char* userid, int reply);
|
||||
void Discord_UpdateHandlers(DiscordEventHandlers* handlers);
|
||||
]]
|
||||
|
||||
local function unpackDiscordUser(request)
|
||||
return ffi.string(request.userId),ffi.string(request.username),
|
||||
ffi.string(request.discriminator),ffi.string(request.avatar)
|
||||
end
|
||||
|
||||
-- callback proxies
|
||||
-- note: callbacks are not JIT compiled (= SLOW), try to avoid doing performance critical tasks in them
|
||||
-- luajit.org/ext_ffi_semantics.html
|
||||
local ready_proxy=ffi.cast("readyPtr",function(request)
|
||||
if RPC.ready then
|
||||
RPC.ready(unpackDiscordUser(request))
|
||||
end
|
||||
end)
|
||||
|
||||
local disconnected_proxy=ffi.cast("disconnectedPtr",function(errorCode,message)
|
||||
if RPC.disconnected then
|
||||
RPC.disconnected(errorCode,ffi.string(message))
|
||||
end
|
||||
end)
|
||||
|
||||
local errored_proxy=ffi.cast("erroredPtr",function(errorCode,message)
|
||||
if RPC.errored then
|
||||
RPC.errored(errorCode,ffi.string(message))
|
||||
end
|
||||
end)
|
||||
|
||||
local joinGame_proxy=ffi.cast("joinGamePtr",function(joinSecret)
|
||||
if RPC.joinGame then
|
||||
RPC.joinGame(ffi.string(joinSecret))
|
||||
end
|
||||
end)
|
||||
|
||||
local spectateGame_proxy=ffi.cast("spectateGamePtr",function(spectateSecret)
|
||||
if RPC.spectateGame then
|
||||
RPC.spectateGame(ffi.string(spectateSecret))
|
||||
end
|
||||
end)
|
||||
|
||||
local joinRequest_proxy=ffi.cast("joinRequestPtr",function(request)
|
||||
if RPC.joinRequest then
|
||||
RPC.joinRequest(unpackDiscordUser(request))
|
||||
end
|
||||
end)
|
||||
|
||||
-- helpers
|
||||
local function checkArg(arg,argType,argName,func,maybeNil)
|
||||
assert(type(arg)==argType or (maybeNil and arg==nil),
|
||||
string.format("Argument \"%s\" to function \"%s\" has to be of type \"%s\"",
|
||||
argName,func,argType))
|
||||
end
|
||||
|
||||
local function checkStrArg(arg,maxLen,argName,func,maybeNil)
|
||||
if maxLen then
|
||||
assert(type(arg)=="string" and arg:len()<=maxLen or (maybeNil and arg==nil),
|
||||
string.format("Argument \"%s\" of function \"%s\" has to be of type string with maximum length %d",
|
||||
argName,func,maxLen))
|
||||
else
|
||||
checkArg(arg,"string",argName,func,true)
|
||||
end
|
||||
end
|
||||
|
||||
local function checkIntArg(arg,maxBits,argName,func,maybeNil)
|
||||
maxBits=math.min(maxBits or 32,52) -- lua number (double) can only store integers < 2^53
|
||||
local maxVal=2^(maxBits-1) -- assuming signed integers, which, for now, are the only ones in use
|
||||
assert(type(arg)=="number" and math.floor(arg)==arg
|
||||
and arg<maxVal and arg>=-maxVal
|
||||
or (maybeNil and arg==nil),
|
||||
string.format("Argument \"%s\" of function \"%s\" has to be a whole number <= %d",
|
||||
argName,func,maxVal))
|
||||
end
|
||||
|
||||
-- function wrappers
|
||||
function RPC.initialize(applicationId,autoRegister,optionalSteamId)
|
||||
local func="discordRPC.Initialize"
|
||||
checkStrArg(applicationId,nil,"applicationId",func)
|
||||
checkArg(autoRegister,"boolean","autoRegister",func)
|
||||
if optionalSteamId~=nil then
|
||||
checkStrArg(optionalSteamId,nil,"optionalSteamId",func)
|
||||
end
|
||||
|
||||
local eventHandlers=ffi.new("struct DiscordEventHandlers")
|
||||
eventHandlers.ready=ready_proxy
|
||||
eventHandlers.disconnected=disconnected_proxy
|
||||
eventHandlers.errored=errored_proxy
|
||||
eventHandlers.joinGame=joinGame_proxy
|
||||
eventHandlers.spectateGame=spectateGame_proxy
|
||||
eventHandlers.joinRequest=joinRequest_proxy
|
||||
|
||||
RPC_C.Discord_Initialize(applicationId,eventHandlers,
|
||||
autoRegister and 1 or 0,optionalSteamId)
|
||||
end
|
||||
|
||||
function RPC.shutdown()
|
||||
RPC_C.Discord_Shutdown()
|
||||
end
|
||||
|
||||
function RPC.runCallbacks()
|
||||
RPC_C.Discord_RunCallbacks()
|
||||
end
|
||||
-- http://luajit.org/ext_ffi_semantics.html#callback :
|
||||
-- It is not allowed, to let an FFI call into a C function (runCallbacks)
|
||||
-- get JIT-compiled, which in turn calls a callback, calling into Lua again (e.g. discordRPC.ready).
|
||||
-- Usually this attempt is caught by the interpreter first and the C function
|
||||
-- is blacklisted for compilation.
|
||||
-- solution:
|
||||
-- "Then you'll need to manually turn off JIT-compilation with jit.off() for
|
||||
-- the surrounding Lua function that invokes such a message polling function."
|
||||
jit.off(RPC.runCallbacks)
|
||||
|
||||
function RPC.updatePresence(presence)
|
||||
local func="discordRPC.updatePresence"
|
||||
checkArg(presence,"table","presence",func)
|
||||
|
||||
-- -1 for string length because of 0-termination
|
||||
checkStrArg(presence.state,127,"presence.state",func,true)
|
||||
checkStrArg(presence.details,127,"presence.details",func,true)
|
||||
|
||||
checkIntArg(presence.startTimestamp,64,"presence.startTimestamp",func,true)
|
||||
checkIntArg(presence.endTimestamp,64,"presence.endTimestamp",func,true)
|
||||
|
||||
checkStrArg(presence.largeImageKey,31,"presence.largeImageKey",func,true)
|
||||
checkStrArg(presence.largeImageText,127,"presence.largeImageText",func,true)
|
||||
checkStrArg(presence.smallImageKey,31,"presence.smallImageKey",func,true)
|
||||
checkStrArg(presence.smallImageText,127,"presence.smallImageText",func,true)
|
||||
checkStrArg(presence.partyId,127,"presence.partyId",func,true)
|
||||
|
||||
checkIntArg(presence.partySize,32,"presence.partySize",func,true)
|
||||
checkIntArg(presence.partyMax,32,"presence.partyMax",func,true)
|
||||
|
||||
checkStrArg(presence.matchSecret,127,"presence.matchSecret",func,true)
|
||||
checkStrArg(presence.joinSecret,127,"presence.joinSecret",func,true)
|
||||
checkStrArg(presence.spectateSecret,127,"presence.spectateSecret",func,true)
|
||||
|
||||
checkIntArg(presence.instance,8,"presence.instance",func,true)
|
||||
|
||||
local cpresence=ffi.new("struct DiscordRichPresence")
|
||||
cpresence.state=presence.state
|
||||
cpresence.details=presence.details
|
||||
cpresence.startTimestamp=presence.startTimestamp or 0
|
||||
cpresence.endTimestamp=presence.endTimestamp or 0
|
||||
cpresence.largeImageKey=presence.largeImageKey
|
||||
cpresence.largeImageText=presence.largeImageText
|
||||
cpresence.smallImageKey=presence.smallImageKey
|
||||
cpresence.smallImageText=presence.smallImageText
|
||||
cpresence.partyId=presence.partyId
|
||||
cpresence.partySize=presence.partySize or 0
|
||||
cpresence.partyMax=presence.partyMax or 0
|
||||
cpresence.matchSecret=presence.matchSecret
|
||||
cpresence.joinSecret=presence.joinSecret
|
||||
cpresence.spectateSecret=presence.spectateSecret
|
||||
cpresence.instance=presence.instance or 0
|
||||
|
||||
RPC_C.Discord_UpdatePresence(cpresence)
|
||||
end
|
||||
|
||||
function RPC.clearPresence()
|
||||
RPC_C.Discord_ClearPresence()
|
||||
end
|
||||
|
||||
local replyMap={
|
||||
no=0,
|
||||
yes=1,
|
||||
ignore=2,
|
||||
}
|
||||
|
||||
-- maybe let reply take ints too (0, 1, 2) and add constants to the module
|
||||
function RPC.respond(userId,reply)
|
||||
checkStrArg(userId,nil,"userId","discordRPC.respond")
|
||||
assert(replyMap[reply],"Argument 'reply' to discordRPC.respond must be 'yes'|'no'|'ignore'")
|
||||
RPC_C.Discord_Respond(userId,replyMap[reply])
|
||||
end
|
||||
|
||||
-- garbage collection callback
|
||||
RPC.gcDummy=newproxy(true)
|
||||
getmetatable(RPC.gcDummy).__gc=function()
|
||||
RPC.shutdown()
|
||||
ready_proxy:free()
|
||||
disconnected_proxy:free()
|
||||
errored_proxy:free()
|
||||
joinGame_proxy:free()
|
||||
spectateGame_proxy:free()
|
||||
joinRequest_proxy:free()
|
||||
end
|
||||
|
||||
function RPC.ready(userId,username,discriminator,avatar)
|
||||
print(string.format("Discord: ready (%s,%s,%s,%s)",userId,username,discriminator,avatar))
|
||||
end
|
||||
function RPC.disconnected(errorCode,message)
|
||||
print(string.format("Discord: disconnected (%d: %s)",errorCode,message))
|
||||
end
|
||||
function RPC.errored(errorCode,message)
|
||||
print(string.format("Discord: error (%d: %s)",errorCode,message))
|
||||
end
|
||||
function RPC.joinGame(joinSecret)
|
||||
print(string.format("Discord: join (%s)",joinSecret))
|
||||
end
|
||||
function RPC.spectateGame(spectateSecret)
|
||||
print(string.format("Discord: spectate (%s)",spectateSecret))
|
||||
end
|
||||
function RPC.joinRequest(userId,username,discriminator,avatar)
|
||||
print(string.format("Discord: join request (%s,%s,%s,%s)",userId,username,discriminator,avatar))
|
||||
RPC.respond(userId,'yes')
|
||||
end
|
||||
RPC.initialize(appId,true)
|
||||
end
|
||||
|
||||
local MyRPC={
|
||||
C=RPC_C,
|
||||
RPC=RPC,
|
||||
presence={
|
||||
startTimestamp=os.time(),
|
||||
state="Loading...",
|
||||
details="",
|
||||
largeImageKey='',
|
||||
largeImageText="Techmino",
|
||||
smallImageKey='',
|
||||
smallImageText="",
|
||||
},
|
||||
}
|
||||
|
||||
---@class DiscordRPC.presence
|
||||
---@field state? string
|
||||
---@field details? string
|
||||
---@field startTimestamp? number
|
||||
---@field endTimestamp? number
|
||||
---@field largeImageKey? string
|
||||
---@field largeImageText? string
|
||||
---@field smallImageKey? string
|
||||
---@field smallImageText? string
|
||||
---@field partyId? string
|
||||
---@field partySize? number
|
||||
---@field partyMax? number
|
||||
---@field matchSecret? string
|
||||
---@field joinSecret? string
|
||||
---@field spectateSecret? string
|
||||
---@field instance? number
|
||||
|
||||
---@overload fun()
|
||||
---@overload fun(state: DiscordRPC.presence)
|
||||
---@param state string
|
||||
---@param details string
|
||||
function MyRPC.update(state,details)
|
||||
if state then
|
||||
for k,v in next,
|
||||
type(state)=='string'
|
||||
and {state=state,details=details}
|
||||
or state
|
||||
do
|
||||
MyRPC.presence[k]=v
|
||||
end
|
||||
end
|
||||
if RPC then RPC.updatePresence(MyRPC.presence) end
|
||||
end
|
||||
|
||||
return MyRPC
|
||||
@@ -26,7 +26,7 @@ end
|
||||
return {
|
||||
das=16,arr=6,
|
||||
sddas=6,sdarr=6,
|
||||
irs=false,ims=false,
|
||||
logicalIRS=false,logicalIMS=false,
|
||||
drop=6,lock=6,
|
||||
wait=10,fall=25,
|
||||
freshLimit=0,
|
||||
|
||||
@@ -26,7 +26,7 @@ end
|
||||
return {
|
||||
das=16,arr=6,
|
||||
sddas=3,sdarr=3,
|
||||
irs=false,ims=false,
|
||||
logicalIRS=false,logicalIMS=false,
|
||||
drop=3,lock=3,
|
||||
wait=10,fall=25,
|
||||
freshLimit=0,
|
||||
|
||||
@@ -26,7 +26,7 @@ end
|
||||
return {
|
||||
das=16,arr=6,
|
||||
sddas=2,sdarr=2,
|
||||
irs=false,ims=false,
|
||||
logicalIRS=false,logicalIMS=false,
|
||||
drop=2,lock=2,
|
||||
wait=10,fall=25,
|
||||
freshLimit=0,
|
||||
|
||||
@@ -7,7 +7,7 @@ end
|
||||
return {
|
||||
das=16,arr=6,
|
||||
sddas=1,sdarr=1,
|
||||
irs=false,ims=false,
|
||||
logicalIRS=false,logicalIMS=false,
|
||||
drop=1,lock=1,
|
||||
wait=10,fall=25,
|
||||
freshLimit=0,
|
||||
|
||||
@@ -149,7 +149,7 @@ return {
|
||||
keyCancel={10,11,12,14,15,16,17,18,19,20},
|
||||
das=16,arr=1,
|
||||
minsdarr=1,
|
||||
ihs=true,irs=true,ims=false,
|
||||
logicalIRS=true,logicalIHS=true,logicalIMS=false,
|
||||
mesDisp=function(P)
|
||||
local D=P.modeData
|
||||
GC.setColor(1,1,1,1)
|
||||
|
||||
@@ -975,7 +975,7 @@ end
|
||||
do-- function dumpBasicConfig()
|
||||
local gameSetting={
|
||||
-- Tuning
|
||||
'das','arr','dascut','dropcut','sddas','sdarr',
|
||||
'das','arr','dascut','irscut','dropcut','sddas','sdarr',
|
||||
'ihs','irs','ims','RS',
|
||||
|
||||
-- System
|
||||
@@ -1021,7 +1021,7 @@ do-- function resetGameData(args)
|
||||
end
|
||||
local gameSetting={
|
||||
-- Tuning
|
||||
'das','arr','dascut','dropcut','sddas','sdarr',
|
||||
'das','arr','dascut','irscut','dropcut','sddas','sdarr',
|
||||
'ihs','irs','ims','RS',
|
||||
|
||||
-- System
|
||||
@@ -1051,6 +1051,8 @@ do-- function resetGameData(args)
|
||||
GAME.rank=0
|
||||
GAME.warnLVL0=0
|
||||
GAME.warnLVL=0
|
||||
GAME.pauseCount=0
|
||||
GAME.pauseTime=0
|
||||
if args:find'r' then
|
||||
GAME.frameStart=0
|
||||
GAME.recording=false
|
||||
@@ -1058,8 +1060,6 @@ do-- function resetGameData(args)
|
||||
else
|
||||
GAME.frameStart=args:find'n' and 0 or 180-SETTING.reTime*60
|
||||
GAME.seed=seed or math.random(1046101471)
|
||||
GAME.pauseTime=0
|
||||
GAME.pauseCount=0
|
||||
GAME.saved=false
|
||||
GAME.setting=_copyGameSetting()
|
||||
GAME.tasUsed=false
|
||||
@@ -1236,7 +1236,7 @@ do-- function pressKey(k)
|
||||
end
|
||||
do-- SETXXX(k) & ROOMXXX(k)
|
||||
local warnList={
|
||||
'das','arr','dascut','dropcut','sddas','sdarr',
|
||||
'das','arr','dascut','irscut','dropcut','sddas','sdarr',
|
||||
'ihs','irs','ims','RS',
|
||||
'frameMul','highCam',
|
||||
'VKSwitch','VKIcon','VKTrack','VKDodge',
|
||||
|
||||
@@ -605,7 +605,7 @@ do-- Userdata tables
|
||||
SETTING={-- Settings
|
||||
-- Tuning
|
||||
das=10,arr=2,
|
||||
dascut=0,dropcut=0,
|
||||
dascut=0,irscut=6,dropcut=0,
|
||||
sddas=0,sdarr=2,
|
||||
ihs=true,irs=true,ims=true,
|
||||
holdMode='hold',
|
||||
@@ -623,7 +623,7 @@ do-- Userdata tables
|
||||
maxFPS=60,
|
||||
frameMul=100,
|
||||
locale='zh',
|
||||
skinSet='crystal_scf',
|
||||
skinSet='Crystal (Scf)',
|
||||
skin={
|
||||
1,7,11,3,14,4,9,
|
||||
1,7,2,6,10,2,13,5,9,15,4,11,3,12,2,16,8,4,
|
||||
|
||||
@@ -865,6 +865,11 @@ FNNS and {"Support 3",
|
||||
"term",
|
||||
"A special delay applied to DAS when a new block is spawned. When this happens, a small delay is added before the DAS starts timing, so that a piece doesn't start moving immediately when a sideways direction key is pressed.\nOther games may have similar mechanisms, but they may work differently.",
|
||||
},
|
||||
{"IRS cut",
|
||||
"irscut icd",
|
||||
"term",
|
||||
"A special delay applied to IRS when a new block is spawned. When entry delay is disabled, this will delay IRS from being applied, allowing you to release the rotation button in the period to avoid a misdrop.",
|
||||
},
|
||||
{"Auto-lock cut",
|
||||
"autolockcut mdcut",
|
||||
"term",
|
||||
|
||||
@@ -961,6 +961,11 @@ FNNS and {"サポート3",
|
||||
"term",
|
||||
"*Techmino用語*通常、ミノが出現する前にDAS時間以上入力をしているとミノが出現した瞬間に動き出します\nDASカットはこのような現象を減らすためにDAS時間以上入力していても出現時にDASカット分減算する機能です\n他のゲームにも似たようなものがありますが恐らく異なるでしょう",
|
||||
},
|
||||
-- {"IRS cut",
|
||||
-- "irscut icd",
|
||||
-- "term",
|
||||
-- "A special delay applied to IRS when a new block is spawned. When entry delay is disabled, this will delay IRS from being applied, allowing you to release the rotation button in the period to avoid a misdrop.",
|
||||
-- },
|
||||
{"Auto-lock cut(自動設置カット)",
|
||||
"autolockcut mdcut 自動 カット",
|
||||
"term",
|
||||
|
||||
@@ -654,6 +654,11 @@ Xem mục tiếp theo để biết thêm.
|
||||
"term",
|
||||
"Cơ chế đặc biệt sẽ được kích hoạt khi gạch mới xuất hiện. Khi kích hoạt, cơ chế này sẽ tăng DAS lên một chút để gạch không tự di chuyển ngay khi đang có phím được giữ.\n\nCác game khác có thể có tính năng tương tự nhưng cách hoạt động có thể khác nhau.",
|
||||
},
|
||||
-- {"IRS cut",
|
||||
-- "irscut icd",
|
||||
-- "term",
|
||||
-- "A special delay applied to IRS when a new block is spawned. When entry delay is disabled, this will delay IRS from being applied, allowing you to release the rotation button in the period to avoid a misdrop.",
|
||||
-- },
|
||||
{"Auto-lock cut",
|
||||
"nhom05e2 autolockcut",
|
||||
"term",
|
||||
|
||||
@@ -618,17 +618,17 @@ FNNS and {"赞助3",
|
||||
"主流方块游戏中七种块的颜色会使用同一套彩虹配色:\nZ:红 S:绿 J:蓝 L:橙 T:紫 O:黄 I:青\n\nTechmino默认也使用这一套 “标准” 配色。",
|
||||
},
|
||||
{"提前旋转(IRS)",
|
||||
"irs initialrotatesystem",
|
||||
"tiqianxuanzhuan irs initialrotatesystem",
|
||||
"term",
|
||||
"Initial Rotation System 提前旋转系统\n方块出现前提前按下旋转后,出现时就是转好的形状,有时可以避免死亡。",
|
||||
},
|
||||
{"提前暂存(IHS)",
|
||||
"ihs initialholdsystem",
|
||||
"tiqianzancun ihs initialholdsystem",
|
||||
"term",
|
||||
"Initial Hold System 提前Hold系统\n方块出现前提前按下hold后,直接出现hold里的方块,有时可以避免死亡。",
|
||||
},
|
||||
{"提前移动(IMS)",
|
||||
"ims initialmovesystem",
|
||||
"tiqianyidong ims initialmovesystem",
|
||||
"term",
|
||||
"Initial Move System 提前移动系统\n方块出现前提前按住移动后,出现时会朝移动方向偏一格,有时可以避免死亡(Techmino限定)。\n注:需要块出现时das已充满",
|
||||
},
|
||||
@@ -853,6 +853,11 @@ FNNS and {"赞助3",
|
||||
"term",
|
||||
"Techmino中指玩家的操作焦点转移到新方块的瞬间,此时减小(重置)DAS计时器,让自动移动不会立刻生效,减少 “移动键松开晚了导致下一块一出来就立即开始移动” 的情况\n注:其他游戏中的DAS打断机制可能和Techmino的有区别,仅供参考。",
|
||||
},
|
||||
{"IRS打断(ICD)",
|
||||
"irscut icd daduan",
|
||||
"term",
|
||||
"(由Electra设计)新方块生成时触发IRS的特殊延迟。在没有生成延迟时,这会让预输入的旋转动作延迟一段时间再生效,允许晚一点松开旋转键防止md。",
|
||||
},
|
||||
{"误硬降打断(HCD)",
|
||||
"autolockcut mdcut daduan",
|
||||
"term",
|
||||
|
||||
@@ -462,14 +462,13 @@ C. Gamepad
|
||||
tas="TAS (T)",
|
||||
},
|
||||
net_menu={
|
||||
league="Tech League",
|
||||
ffa="FFA",
|
||||
galaxim="Galaxim",
|
||||
rooms="Rooms",
|
||||
resetPW="Reset password",
|
||||
logout="Log out",
|
||||
},
|
||||
net_league={
|
||||
match="Find Match",
|
||||
net_galaxim={
|
||||
match="Enter Sim.",
|
||||
},
|
||||
net_rooms={
|
||||
password="Password",
|
||||
@@ -588,6 +587,7 @@ C. Gamepad
|
||||
bg_on="Normal BG",
|
||||
bg_off="No BG",
|
||||
bg_custom="Custom BG",
|
||||
bg_custom_base64="Paste image as BG\n(PNG/JPG in Base64)",
|
||||
defaultBG="Default BG",
|
||||
resetDbg="Reset to default",
|
||||
lockBG="Lock BG",
|
||||
@@ -623,6 +623,7 @@ C. Gamepad
|
||||
|
||||
das="DAS",arr="ARR",
|
||||
dascut="DAS Cut",
|
||||
irscut="IRS Cut",
|
||||
dropcut="Auto-lock Cut",
|
||||
sddas="Soft Drop DAS",sdarr="Soft Drop ARR",
|
||||
ihs="Initial Hold",
|
||||
@@ -996,7 +997,7 @@ C. Gamepad
|
||||
"1 next 1 hold!",
|
||||
"1 next 6 hold!",
|
||||
"20G is actually a brand new game rule!",
|
||||
"40-line Sprint WR: 13.650s by WestL",
|
||||
"40-line Sprint WR: 13.430s by WestL",
|
||||
"6 next 1 hold!",
|
||||
"6 next 6 hold?!",
|
||||
"A choke a day keeps record away",
|
||||
|
||||
@@ -423,14 +423,13 @@ return {
|
||||
tas="TAS (T)",
|
||||
},
|
||||
net_menu={
|
||||
league="Liga Tech",
|
||||
ffa="FFA",
|
||||
-- galaxim="Galaxim", -- Galaxy+Simulation
|
||||
rooms="Salas",
|
||||
resetPW="Restabl. Contraseña",
|
||||
logout="Desconec.",
|
||||
},
|
||||
net_league={
|
||||
match="Buscar Match",
|
||||
net_galaxim={
|
||||
-- match="Enter Sim.", -- (Actively) Enter (the) (digital) Simulation of (a galaxy)
|
||||
},
|
||||
net_rooms={
|
||||
password="Contraseña",
|
||||
@@ -583,6 +582,7 @@ return {
|
||||
|
||||
das="DAS",arr="ARR",
|
||||
dascut="Intrrp. de DAS",
|
||||
irscut="Intrrp. de IRS",
|
||||
dropcut="Intrrp. de Autocaída",
|
||||
sddas="DAS de C. Ráp.",sdarr="ARR de C. Rápida",
|
||||
ihs="Resv. Inicial",
|
||||
|
||||
@@ -398,14 +398,13 @@ return {
|
||||
-- tas="TAS (T)",
|
||||
},
|
||||
net_menu={
|
||||
-- league="Tech League",
|
||||
ffa="FFA",
|
||||
-- galaxim="Galaxim", -- Galaxy+Simulation
|
||||
rooms="Salons",
|
||||
resetPW="Réinitialiser le mot de passe",
|
||||
logout="Se déconnecter",
|
||||
},
|
||||
net_league={
|
||||
match="Find Match",
|
||||
net_galaxim={
|
||||
-- match="Enter Sim.", -- (Actively) Enter (the) (digital) Simulation of (a galaxy)
|
||||
},
|
||||
net_rooms={
|
||||
password="Mot de passe",
|
||||
@@ -560,6 +559,7 @@ return {
|
||||
|
||||
das="DAS",arr="ARR",
|
||||
dascut="DAS cut",
|
||||
irscut="IRS cut",
|
||||
-- dropcut="Auto-lock cut",
|
||||
sddas="DAS de chute rapide",sdarr="ARR de chute rapide",
|
||||
ihs="Réserve Initiale",
|
||||
|
||||
@@ -424,14 +424,13 @@ return {
|
||||
tas="TAS (T)",
|
||||
},
|
||||
net_menu={
|
||||
league="Tech League",
|
||||
ffa="FFA",
|
||||
-- galaxim="Galaxim", -- Galaxy+Simulation
|
||||
rooms="Ruang-ruang",
|
||||
-- resetPW="Reset password",
|
||||
logout="Log out",
|
||||
},
|
||||
net_league={
|
||||
match="Cari Tandingan",
|
||||
net_galaxim={
|
||||
-- match="Enter Sim.", -- (Actively) Enter (the) (digital) Simulation of (a galaxy)
|
||||
},
|
||||
net_rooms={
|
||||
password="Password",
|
||||
@@ -585,6 +584,7 @@ return {
|
||||
|
||||
das="DAS",arr="ARR",
|
||||
dascut="Gangguan DAS",
|
||||
irscut="Gangguan IRS",
|
||||
dropcut="Gangguan Auto-kunci",
|
||||
sddas="DAS Jatuh",sdarr="ARR Jatuh",
|
||||
ihs="Simpan Saat Tunda",
|
||||
@@ -953,7 +953,7 @@ return {
|
||||
"↑↑↓↓←→←→BA",
|
||||
"$include<studio.h>",
|
||||
"20G sebenarnya peraturan permainan baru!",
|
||||
"Rekor dunia 40L: 13.650s dari WestL",
|
||||
"Rekor dunia 40L: 13.430s dari WestL",
|
||||
"Sistem pencapaian segera akan datang!",
|
||||
"ALL SPIN!",
|
||||
"Am G F G",
|
||||
|
||||
@@ -468,14 +468,13 @@ C. ゲームパッド
|
||||
tas="TAS (T)",
|
||||
},
|
||||
net_menu={
|
||||
league="テクリーグ",
|
||||
ffa="FFA",
|
||||
-- galaxim="Galaxim", -- Galaxy+Simulation
|
||||
rooms="ルーム",
|
||||
resetPW="パスワード再設定",
|
||||
logout="ログアウト",
|
||||
},
|
||||
net_league={
|
||||
match="対戦相手を探す",
|
||||
net_galaxim={
|
||||
-- match="Enter Sim.", -- (Actively) Enter (the) (digital) Simulation of (a galaxy)
|
||||
},
|
||||
net_rooms={
|
||||
password="パスワード",
|
||||
@@ -629,6 +628,7 @@ C. ゲームパッド
|
||||
|
||||
das="DAS",arr="ARR",
|
||||
dascut="DASカット",
|
||||
irscut="IRSカット",
|
||||
dropcut="自動設置カット",
|
||||
sddas="ソフトドロップDAS",sdarr="ソフトドロップARR",
|
||||
ihs="先行ホールド",
|
||||
@@ -1006,7 +1006,7 @@ getTip={refuseCopy=true,
|
||||
"20PCって何?",
|
||||
"26TSDって何?",
|
||||
"2つの回転を使ってみよう、3つ使うとさらにいいです!",
|
||||
"40-line Sprint WR: 13.650s by WestL",
|
||||
"40-line Sprint WR: 13.430s by WestL",
|
||||
"6next 1hold!",
|
||||
"6next 6hold?!",
|
||||
"低音を響かせろ!",
|
||||
|
||||
@@ -412,13 +412,12 @@ return {
|
||||
-- tas="TAS (T)",
|
||||
},
|
||||
net_menu={
|
||||
-- league="Tech League",
|
||||
ffa="FFA",
|
||||
-- galaxim="Galaxim", -- Galaxy+Simulation
|
||||
rooms="Salas",
|
||||
-- resetPW="Reset password",
|
||||
-- logout="Log out",
|
||||
},
|
||||
net_league={
|
||||
net_galaxim={
|
||||
-- match="Find Match",
|
||||
},
|
||||
net_rooms={
|
||||
@@ -573,6 +572,7 @@ return {
|
||||
|
||||
das="DAS",arr="ARR",
|
||||
dascut="DAS cut",
|
||||
irscut="IRS cut",
|
||||
-- dropcut="Auto-lock cut",
|
||||
sddas="Soft Drop DAS",sdarr="Soft Drop ARR",
|
||||
ihs="Segurar Inicial",
|
||||
@@ -948,7 +948,7 @@ return {
|
||||
"1next 1hold!",
|
||||
"1next 6hold!",
|
||||
"Na verdade 20G é uma regra de jogo nova.",
|
||||
"40-line Sprint WR: 13.650s by WestL",
|
||||
"40-line Sprint WR: 13.430s by WestL",
|
||||
"6next 1hold!",
|
||||
"6next 6hold?!",
|
||||
"ALL SPIN!",
|
||||
|
||||
@@ -171,13 +171,12 @@ return {
|
||||
tas="#&; (T)",
|
||||
},
|
||||
net_menu={
|
||||
league="TL",
|
||||
ffa="FFA",
|
||||
-- galaxim="Galaxim", -- Galaxy+Simulation
|
||||
rooms="< >",
|
||||
resetPW="R ***",
|
||||
logout="@_@x",
|
||||
},
|
||||
net_league={
|
||||
net_galaxim={
|
||||
match="!",
|
||||
},
|
||||
net_rooms={
|
||||
@@ -330,6 +329,7 @@ return {
|
||||
|
||||
das="x---x x x",arr="x x-x-x",
|
||||
dascut="x x ↓___x x",
|
||||
irscut="'' !''x",
|
||||
dropcut="↓_ !↓↓x",
|
||||
sddas="↓---↓ ↓ ↓",sdarr="↓ ↓-↓-↓",
|
||||
ihs="![ ]",
|
||||
|
||||
@@ -197,7 +197,7 @@ return {
|
||||
|
||||
|
||||
keySettingInstruction="Nhấn một phím để gán phím đó\nescape (esc): Hủy\nbackspace: Xoá",
|
||||
customBGhelp=not MOBILE and "Kéo một tấm ảnh vào đây để áp dụng ảnh nền tuỳ chỉnh" or "Chưa hỗ trợ ảnh nền cho điện thoại",
|
||||
customBGhelp="Kéo một tấm ảnh vào đây để áp dụng ảnh nền tuỳ chỉnh",
|
||||
customBGloadFailed="Định dạng ảnh không được hỗ trợ",
|
||||
|
||||
errorMsg="Techmino bị lỗi và cần phải được khởi động lại\nBạn có thể gửi error log để giúp dev sửa game nhanh hơn.",
|
||||
@@ -457,14 +457,13 @@ C. Tay cầm chơi game (Gamepad):
|
||||
tas="TAS (T)",
|
||||
},
|
||||
net_menu={
|
||||
league="Tech League",
|
||||
ffa="FFA",
|
||||
galaxim="Galaxim",
|
||||
rooms="Danh sách phòng",
|
||||
resetPW="Đặt lại mật khẩu",
|
||||
logout="Đăng xuất",
|
||||
},
|
||||
net_league={
|
||||
match="Tìm trận",
|
||||
net_galaxim={
|
||||
match="Bước vào mô phỏng",
|
||||
},
|
||||
net_rooms={
|
||||
password="Mật khẩu",
|
||||
@@ -584,6 +583,7 @@ C. Tay cầm chơi game (Gamepad):
|
||||
bg_on="Ảnh nền thường",
|
||||
bg_off="Không ảnh nền",
|
||||
bg_custom="Ảnh nền tự chọn",
|
||||
bg_custom_base64="Dán ảnh và cài thành ảnh nền\n(PNG/JPG ở format Base64)",
|
||||
defaultBG="Nền mặc định",
|
||||
resetDbg='Đặt lại',
|
||||
lockBG="Khóa ảnh nền",
|
||||
@@ -619,6 +619,7 @@ C. Tay cầm chơi game (Gamepad):
|
||||
|
||||
das="DAS",arr="ARR",
|
||||
dascut="DAS cut",
|
||||
irscut="IRS cut",
|
||||
dropcut="Auto-lock cut",
|
||||
sddas="DAS thả nhẹ",sdarr="ARR thả nhẹ",
|
||||
ihs="Giữ tức thì",
|
||||
@@ -964,7 +965,7 @@ C. Tay cầm chơi game (Gamepad):
|
||||
['tech_l']= {"Tech B2B", "RẤT KHÓ", "Cố gắng không phá B2B!"},
|
||||
['tech_l_plus']= {"Tech B2B", "RẤT KHÓ+", "Chỉ được clear Spin hoặc PC"},
|
||||
['tech_finesse']= {"Tech FINESSE", "", "Không được phép có lỗi di chuyển!"},
|
||||
['tech_finesse_f']= {"Tech FINESSE", "KHÔNG ĐƠN/ĐÔI", "Không được phép có lỗi di chuyển hoặc kiểu Xoá hàng thường!"},
|
||||
['tech_finesse_f']= {"Tech FINESSE", "PLUS", "Không được phép có lỗi di chuyển hoặc kiểu Xoá hàng thường!"},
|
||||
['tsd_e']= {"TSD Challenge", "DỄ", "Chỉ được làm T-Spin Double!"},
|
||||
['tsd_h']= {"TSD Challenge", "KHÓ", "Chỉ được làm T-Spin Double!"},
|
||||
['tsd_u']= {"TSD Challenge", "THÁCH ĐẤU", "Chỉ được làm T-Spin Double!"},
|
||||
@@ -1003,7 +1004,7 @@ C. Tay cầm chơi game (Gamepad):
|
||||
"6 next 1 hold!",
|
||||
"6 next 6 hold?!",
|
||||
"20G thực chất là một chế độ mới đấy!",
|
||||
"Kỷ lục Sprint 40 hàng: 13.650s (WestL)",
|
||||
"Kỷ lục Sprint 40 hàng: 13.430s (WestL)",
|
||||
"Rất gần nhưng lại rất xa",
|
||||
"ALL SPIN!",
|
||||
"Am G F G",
|
||||
|
||||
@@ -453,14 +453,13 @@ return {
|
||||
tas="TAS (T)",
|
||||
},
|
||||
net_menu={
|
||||
league="Tech League",
|
||||
ffa="FFA",
|
||||
galaxim="Galaxim",
|
||||
rooms="房间列表",
|
||||
resetPW="重置密码",
|
||||
logout="退出登录",
|
||||
},
|
||||
net_league={
|
||||
match="匹配对手",
|
||||
net_galaxim={
|
||||
match="进入模拟",
|
||||
},
|
||||
net_rooms={
|
||||
password="密码",
|
||||
@@ -613,6 +612,7 @@ return {
|
||||
|
||||
das="DAS:",arr="ARR:",
|
||||
dascut="DAS打断:",
|
||||
irscut="IRS打断",
|
||||
dropcut="误硬降打断:",
|
||||
sddas="软降DAS:",sdarr="软降ARR:",
|
||||
ihs="提前Hold",
|
||||
@@ -984,7 +984,7 @@ return {
|
||||
"1next 6hold!",
|
||||
"3.1415926535897932384(\\d{3})",
|
||||
"3next 1hold?",
|
||||
"40行世界纪录: 13.650s by WestL",
|
||||
"40行世界纪录: 13.430s by WestL",
|
||||
"6236326236327175",
|
||||
"626in1",
|
||||
"6next 1hold!",
|
||||
@@ -1207,7 +1207,7 @@ return {
|
||||
"豆知识[016]本游戏的B2B是气槽机制,和传统的开关机制不一样哦",
|
||||
"豆知识[017]本游戏内置了几个休(yìng)闲(hé)小游戏哦~",
|
||||
"豆知识[018]本游戏在设计的时候受到了大量其他块游甚至一些音游的启发",
|
||||
"豆知识[019]必须要软降才能到达的位置都会判定为极简操作",
|
||||
"豆知识[019]必须要软降才能到达的位置不会被判定为非极简操作",
|
||||
"豆知识[020]别看攻击效率不高,其实消四还是很强的",
|
||||
"豆知识[021]别问游戏名字怎么取的,问就是随便想的",
|
||||
"豆知识[022]不同人打40行最合适的方式不一样,s1w/63/散消/s2w……",
|
||||
|
||||
@@ -372,14 +372,13 @@ return {
|
||||
tas="TAS(); (T)",
|
||||
},
|
||||
net_menu={
|
||||
league="M.TechLeague();",
|
||||
ffa="M.FFA",
|
||||
galaxim="M.Galaxim();",
|
||||
rooms="M.Rooms();",
|
||||
resetPW="M.ResetPW",
|
||||
logout="M.Logout();",
|
||||
},
|
||||
net_league={
|
||||
match="TL.Match();",
|
||||
net_galaxim={
|
||||
match="GX.Enter();",
|
||||
},
|
||||
net_rooms={
|
||||
password="Password=",
|
||||
@@ -532,6 +531,7 @@ return {
|
||||
|
||||
das="Set.DAS=",arr="Set.SRR=",
|
||||
dascut="Set.DASCut=",
|
||||
irscut="Set.IRSCut=",
|
||||
dropcut="Set.DropCut=",
|
||||
sddas="Set.SDDAS=",sdarr="Set.SDARR=",
|
||||
ihs="Set.IHS",
|
||||
|
||||
@@ -424,14 +424,13 @@ return {
|
||||
tas="TAS (T)",
|
||||
},
|
||||
net_menu={
|
||||
league="Tech League",
|
||||
ffa="FFA",
|
||||
galaxim="Galaxim",
|
||||
rooms="房間列表",
|
||||
resetPW="重設密碼",
|
||||
logout="登出",
|
||||
},
|
||||
net_league={
|
||||
match="匹配對手",
|
||||
net_galaxim={
|
||||
match="进入模拟",
|
||||
},
|
||||
net_rooms={
|
||||
password="密碼",
|
||||
@@ -584,6 +583,7 @@ return {
|
||||
|
||||
das="DAS",arr="ARR",
|
||||
dascut="DAS打斷",
|
||||
irscut="IRS打斷",
|
||||
dropcut="誤硬降打斷",
|
||||
sddas="軟降DAS",sdarr="軟降ARR",
|
||||
ihs="提前Hold",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
require'parts.scenes.customGame'.initialize()
|
||||
|
||||
return {
|
||||
env={},
|
||||
load=function()
|
||||
@@ -12,10 +14,11 @@ return {
|
||||
PLY.newPlayer(1)
|
||||
local AItype=GAME.modeEnv.opponent:sub(1,2)
|
||||
local AIlevel=tonumber(GAME.modeEnv.opponent:sub(-1))
|
||||
local useHold=GAME.modeEnv.holdCount>0 and GAME.modeEnv.holdMode=='hold'
|
||||
if AItype=='9S' then
|
||||
PLY.newAIPlayer(2,BOT.template{type='9S',speedLV=2*AIlevel,hold=GAME.modeEnv.holdCount})
|
||||
PLY.newAIPlayer(2,BOT.template{type='9S',speedLV=2*AIlevel,hold=useHold})
|
||||
elseif AItype=='CC' then
|
||||
PLY.newAIPlayer(2,BOT.template{type='CC',speedLV=2*AIlevel-1,next=math.floor(AIlevel*.5+1),hold=GAME.modeEnv.holdCount,node=20000+5000*AIlevel})
|
||||
PLY.newAIPlayer(2,BOT.template{type='CC',speedLV=2*AIlevel-1,next=math.floor(AIlevel*.5+1),hold=useHold,node=20000+5000*AIlevel})
|
||||
end
|
||||
|
||||
for _,P in next,PLY_ALIVE do
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
local gc_setColor,gc_draw=love.graphics.setColor,love.graphics.draw
|
||||
local ply_applyField=PLY.draw.applyField
|
||||
|
||||
require'parts.scenes.customGame'.initialize()
|
||||
|
||||
return {
|
||||
env={
|
||||
fkey1=function(P) P.modeData.showMark=1-P.modeData.showMark end,
|
||||
@@ -54,10 +56,11 @@ return {
|
||||
local AItype=GAME.modeEnv.opponent:sub(1,2)
|
||||
local AIlevel=tonumber(GAME.modeEnv.opponent:sub(-1))
|
||||
PLY.newPlayer(1)
|
||||
local useHold=GAME.modeEnv.holdCount>0 and GAME.modeEnv.holdMode=='hold'
|
||||
if AItype=='9S' then
|
||||
PLY.newAIPlayer(2,BOT.template{type='9S',speedLV=2*AIlevel,hold=GAME.modeEnv.holdCount})
|
||||
PLY.newAIPlayer(2,BOT.template{type='9S',speedLV=2*AIlevel,hold=useHold})
|
||||
elseif AItype=='CC' then
|
||||
PLY.newAIPlayer(2,BOT.template{type='CC',speedLV=2*AIlevel-1,next=math.floor(AIlevel*.5+1),hold=GAME.modeEnv.holdCount,node=20000+5000*AIlevel})
|
||||
PLY.newAIPlayer(2,BOT.template{type='CC',speedLV=2*AIlevel-1,next=math.floor(AIlevel*.5+1),hold=useHold,node=20000+5000*AIlevel})
|
||||
end
|
||||
end,
|
||||
savePrivate=function()
|
||||
|
||||
@@ -55,6 +55,12 @@ return {
|
||||
P.modeData.colorSet[i]=P.holeRND:random(25)
|
||||
end
|
||||
P.randomizer_spinren=randomizer(P.holeRND)
|
||||
-- table.insert(P.field,1,LINE.new(0))
|
||||
-- P.field[1][1]=18
|
||||
-- P.field[1][2]=18
|
||||
-- P.field[1][9]=18
|
||||
-- P.field[1][10]=18
|
||||
-- table.insert(P.visTime,1,LINE.new(20))
|
||||
P:pushLineList(get_lines(18,P))
|
||||
P.fieldBeneath=0
|
||||
end,
|
||||
|
||||
@@ -29,8 +29,8 @@ local NET={
|
||||
|
||||
onlineCount="_",
|
||||
|
||||
textBox=WIDGET.newTextBox{name='texts',x=340,y=80,w=600,h=560},
|
||||
inputBox=WIDGET.newInputBox{name='input',x=340,y=660,w=600,h=50,limit=256},
|
||||
textBox=WIDGET.newTextBox{name='texts',x=20,y=110,w=980,h=500},
|
||||
inputBox=WIDGET.newInputBox{name='input',x=20,y=630,w=980,h=50,limit=256},
|
||||
}
|
||||
|
||||
function NET.freshRoomAllReady()
|
||||
@@ -187,17 +187,48 @@ function NET.getAvatar(uid)
|
||||
end
|
||||
end)
|
||||
end
|
||||
function NET.getNotice(lang,count)
|
||||
|
||||
local noticeLang={
|
||||
en='en_us',
|
||||
fr='en_us', -- fr_fr
|
||||
es='en_us', -- es_es
|
||||
id='en_us', -- id_id
|
||||
pt='en_us', -- pt_pt
|
||||
symbol='en_us', -- gl_os
|
||||
ja='en_us', -- ja_jp
|
||||
vi='en_us', -- vi_vn
|
||||
zh='zh_cn',
|
||||
zh_trad='zh_tw',
|
||||
zh_code='zh_cn',
|
||||
}
|
||||
function NET.launchNotice()
|
||||
TASK.new(function()
|
||||
local res=getMsg({
|
||||
pool='getNotice',
|
||||
path='/techmino/api/v1/notice?language='..noticeLang[SETTING.locale]..'&lastCount=1',
|
||||
},6.26)
|
||||
|
||||
if res and res.code==200 then
|
||||
local opt=res.data.contents[1]
|
||||
if opt then
|
||||
MES.new('info',opt.content,12.6)
|
||||
else
|
||||
MES.new('info',text.Techrater.NoticeManager.noticeNotFound)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
function NET.getNotice(count)
|
||||
WAIT{timeout=6.26}
|
||||
TASK.new(function()
|
||||
local res=getMsg({
|
||||
pool='getNotice',
|
||||
path='/techmino/api/v1/notice?language='..(lang or 'zh_cn')..'&lastCount='..(count or 5),
|
||||
path='/techmino/api/v1/notice?language='..noticeLang[SETTING.locale]..'&lastCount='..(count or 5),
|
||||
},6.26)
|
||||
|
||||
if res and res.code==200 and type(res.data)=='string' then
|
||||
local dataStr=""
|
||||
SCN.go('notice',nil,dataStr)
|
||||
if res and res.code==200 then
|
||||
WAIT.interrupt()
|
||||
SCN.go('notice',nil,noticeLang[SETTING.locale],res.data.contents)
|
||||
end
|
||||
end)
|
||||
end
|
||||
@@ -393,10 +424,19 @@ function NET.wsCallBack.room_chat(body)
|
||||
if SCN.cur~='net_game' then return end
|
||||
TASK.unlock('receiveMessage')
|
||||
TASK.lock('receiveMessage',1)
|
||||
NET.textBox:push{
|
||||
COLOR.Z,_getFullName(body.data.playerId).." ",
|
||||
COLOR.N,body.data.message,
|
||||
}
|
||||
|
||||
local name=_getFullName(body.data.playerId).." "
|
||||
-- P/s: we need to wrap both name and message, not just only message
|
||||
local _,msgWrapped=FONT.get(NET.inputBox.font):getWrap(name..body.data.message,950)
|
||||
-- We don't want to see the name repeat twice :skull:
|
||||
msgWrapped[1]=string.gsub(msgWrapped[1],name,"",1)
|
||||
-- Push the name in white and first line of message in blue first
|
||||
NET.textBox:push{COLOR.Z,name,COLOR.N,msgWrapped[1]}
|
||||
for i, line in ipairs(msgWrapped) do
|
||||
if i ~= 1 then
|
||||
NET.textBox:push{COLOR.N,msgWrapped[i]}
|
||||
end
|
||||
end
|
||||
end
|
||||
function NET.wsCallBack.room_create(body)
|
||||
MES.new('check',text.createRoomSuccessed)
|
||||
|
||||
@@ -29,11 +29,13 @@ return {
|
||||
{font=65,name="怀沙"},
|
||||
{font=65,name="星街书婉"},
|
||||
{font=65,name="老板来两份薯条"},
|
||||
{font=65,name="yumucy"},
|
||||
{font=65,name="[**昆]"},
|
||||
{font=65,name="[**浩]"},
|
||||
{font=65,name="sakurw"},
|
||||
{font=65,name="[**霖]"},
|
||||
{font=65,name="KK"},
|
||||
{font=65,name="武仰绍"},
|
||||
|
||||
{font=25,name="八零哥"},
|
||||
{font=25,name="蕴空之灵"},
|
||||
@@ -151,4 +153,9 @@ return {
|
||||
{font=25,name="电蜥蜴"},
|
||||
{font=25,name="浮芙fufu"},
|
||||
{font=25,name="NOname_awa"},
|
||||
{font=65,name="liaotianhao"},
|
||||
{font=65,name="Aquamarine"},
|
||||
{font=65,name="jacky"},
|
||||
{font=65,name="方块丶小刘"},
|
||||
{font=65,name="DX3906"},
|
||||
}
|
||||
|
||||
@@ -177,12 +177,12 @@ local function _drawField(P,showInvis)
|
||||
if P.falling==0 then-- Blocks only
|
||||
if ENV.upEdge then
|
||||
gc_setShader(shader_lighter)
|
||||
gc_translate(0,-4)
|
||||
gc_translate(0,-6)
|
||||
-- <drawRow>
|
||||
for j=start,min(start+21,#F) do _drawRow(texture,j,V[j],F[j]) end
|
||||
-- </drawRow>
|
||||
gc_setShader(shader_fieldSatur)
|
||||
gc_translate(0,4)
|
||||
gc_translate(0,6)
|
||||
else
|
||||
gc_setShader(shader_fieldSatur)
|
||||
end
|
||||
@@ -197,7 +197,7 @@ local function _drawField(P,showInvis)
|
||||
if ENV.upEdge then
|
||||
gc_push('transform')
|
||||
gc_setShader(shader_lighter)
|
||||
gc_translate(0,-4)
|
||||
gc_translate(0,-6)
|
||||
-- <drawRow>
|
||||
for j=start,min(start+21,#F) do
|
||||
while j==P.clearingRow[h] do
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
return {
|
||||
das=10,arr=2,
|
||||
dascut=0,dropcut=0,
|
||||
dascut=0,irscut=6,dropcut=0,
|
||||
sddas=2,sdarr=2,
|
||||
ihs=true,irs=true,ims=true,
|
||||
logicalIHS=true,logicalIRS=true,logicalIMS=true,
|
||||
|
||||
ghostType='gray',
|
||||
block=true,ghost=.3,center=1,
|
||||
@@ -40,7 +41,7 @@ return {
|
||||
RS='TRS',
|
||||
sequence='bag',
|
||||
seqData={1,2,3,4,5,6,7},
|
||||
skinSet='crystal_scf',
|
||||
skinSet='Crystal (Scf)',
|
||||
face=false,skin=false,
|
||||
mission=false,
|
||||
|
||||
@@ -62,10 +63,16 @@ return {
|
||||
mindas=0,minarr=0,minsdarr=0,
|
||||
noInitSZO=false,
|
||||
|
||||
mesDisp={},
|
||||
hook_drop={},
|
||||
hook_die={},
|
||||
task={},
|
||||
-- Some Events are registered in player/init.lua, see "tableNeedMerge"
|
||||
extraEvent={
|
||||
{'attack',4},
|
||||
},
|
||||
extraEventHandler={
|
||||
attack=function(P,P2,...)
|
||||
P:beAttacked(P2,...)
|
||||
end,
|
||||
},
|
||||
|
||||
eventSet="X",
|
||||
|
||||
bg='none',bgm='race',
|
||||
|
||||
@@ -236,17 +236,8 @@ local function _loadRemoteEnv(P,confStr)-- Load gameEnv
|
||||
end
|
||||
end
|
||||
end
|
||||
local function _mergeFuncTable(f,L)
|
||||
if type(f)=='function' then
|
||||
ins(L,f)
|
||||
elseif type(f)=='table' then
|
||||
for i=1,#f do
|
||||
ins(L,f[i])
|
||||
end
|
||||
end
|
||||
return L
|
||||
end
|
||||
local hooks = {
|
||||
local tableNeedMerge={
|
||||
'task',
|
||||
'mesDisp',
|
||||
'hook_left',
|
||||
'hook_left_manual',
|
||||
@@ -259,36 +250,63 @@ local hooks = {
|
||||
'hook_spawn',
|
||||
'hook_hold',
|
||||
'hook_die',
|
||||
'task'
|
||||
'extraEvent',
|
||||
}
|
||||
for _,k in next,tableNeedMerge do gameEnv0[k]={} end
|
||||
local function _mergeFuncTable(f,L)
|
||||
if type(f)=='function' then
|
||||
ins(L,f)
|
||||
elseif type(f)=='table' then
|
||||
for i=1,#f do
|
||||
ins(L,f[i])
|
||||
end
|
||||
end
|
||||
return L
|
||||
end
|
||||
local function _applyGameEnv(P)-- Finish gameEnv processing
|
||||
local ENV=P.gameEnv
|
||||
|
||||
-- Apply events
|
||||
for i=1,#hooks do
|
||||
ENV[hooks[i]]=_mergeFuncTable(ENV[hooks[i]],{})
|
||||
-- Create event tables
|
||||
for i=1,#tableNeedMerge do
|
||||
ENV[tableNeedMerge[i]]=_mergeFuncTable(ENV[tableNeedMerge[i]],{})
|
||||
end
|
||||
|
||||
-- Apply eventSet
|
||||
if ENV.eventSet and ENV.eventSet~="X" then
|
||||
if type(ENV.eventSet)=='string' then
|
||||
local eventSet=require('parts.eventsets.'..ENV.eventSet)
|
||||
if eventSet then
|
||||
for k,v in next,eventSet do
|
||||
if TABLE.find(hooks,k) then
|
||||
_mergeFuncTable(v,ENV[k])
|
||||
elseif type(v)=='table' then
|
||||
ENV[k]=TABLE.copy(v)
|
||||
while true do
|
||||
if not (ENV.eventSet and ENV.eventSet~="X") then
|
||||
break
|
||||
end
|
||||
if type(ENV.eventSet)~='string' then
|
||||
MES.new('warn',"Wrong event set type: "..type(ENV.eventSet))
|
||||
break
|
||||
end
|
||||
local eventSet=require('parts.eventsets.'..ENV.eventSet)
|
||||
if not eventSet then
|
||||
MES.new('warn',"No event set called: "..ENV.eventSet)
|
||||
break
|
||||
end
|
||||
for k,v in next,eventSet do
|
||||
if k=='extraEventHandler' then
|
||||
for ev,handler in next,v do
|
||||
if ENV.extraEventHandler[ev] then
|
||||
local prevHandler=ENV.extraEventHandler[ev]
|
||||
ENV.extraEventHandler[ev]=function(...)
|
||||
prevHandler(...)
|
||||
handler(...)
|
||||
end
|
||||
else
|
||||
ENV[k]=v
|
||||
ENV.extraEventHandler[ev]=handler
|
||||
end
|
||||
end
|
||||
elseif TABLE.find(tableNeedMerge,k) then
|
||||
_mergeFuncTable(v,ENV[k])
|
||||
elseif type(v)=='table' then
|
||||
ENV[k]=TABLE.copy(v)
|
||||
else
|
||||
MES.new('warn',"No event set called: "..ENV.eventSet)
|
||||
ENV[k]=v
|
||||
end
|
||||
else
|
||||
MES.new('warn',"Wrong event set type: "..type(ENV.eventSet))
|
||||
end
|
||||
break
|
||||
end
|
||||
|
||||
P._20G=ENV.drop==0
|
||||
|
||||
@@ -270,27 +270,61 @@ function Player:act_rotRight()
|
||||
if not self.control then return end
|
||||
if self.cur then
|
||||
self.ctrlCount=self.ctrlCount+1
|
||||
if self.bufferedIRS then
|
||||
-- Ensure IRS is spent before the rotation is processed so it doesn't throw things off.
|
||||
-- This is so that if you for instance, are holding left IRS and then rotate right, it doesn't process
|
||||
-- the left and right rotates in the reverse order.
|
||||
self.keyPressing[3]=false
|
||||
self:resolveIRS()
|
||||
self.keyPressing[3]=true
|
||||
end
|
||||
self:spin(1)
|
||||
self:_triggerEvent('hook_rotate',1)
|
||||
self.keyPressing[3]=false
|
||||
|
||||
-- Disable held inputs if IRS is off
|
||||
if not self.gameEnv.irs then
|
||||
self.keyPressing[3]=false
|
||||
end
|
||||
end
|
||||
end
|
||||
function Player:act_rotLeft()
|
||||
if not self.control then return end
|
||||
if self.cur then
|
||||
self.ctrlCount=self.ctrlCount+1
|
||||
if self.bufferedIRS then
|
||||
-- Ensure IRS is spent before the rotation is processed so it doesn't throw things off.
|
||||
-- This is so that if you for instance, are holding left IRS and then rotate right, it doesn't process
|
||||
-- the left and right rotates in the reverse order.
|
||||
self.keyPressing[4]=false
|
||||
self:resolveIRS()
|
||||
self.keyPressing[4]=true
|
||||
end
|
||||
self:spin(3)
|
||||
self:_triggerEvent('hook_rotate',3)
|
||||
self.keyPressing[4]=false
|
||||
-- Disable held inputs if IRS is off
|
||||
if not self.gameEnv.irs then
|
||||
self.keyPressing[4]=false
|
||||
end
|
||||
end
|
||||
end
|
||||
function Player:act_rot180()
|
||||
if not self.control then return end
|
||||
if self.cur then
|
||||
self.ctrlCount=self.ctrlCount+2
|
||||
if self.bufferedIRS then
|
||||
-- Ensure IRS is spent before the rotation is processed so it doesn't throw things off.
|
||||
-- This is so that if you for instance, are holding left IRS and then rotate right, it doesn't process
|
||||
-- the left and right rotates in the reverse order.
|
||||
self.keyPressing[5]=false
|
||||
self:resolveIRS()
|
||||
self.keyPressing[5]=true
|
||||
end
|
||||
self:spin(2)
|
||||
self:_triggerEvent('hook_rotate',2)
|
||||
self.keyPressing[5]=false
|
||||
-- Disable held inputs if IRS is off
|
||||
if not self.gameEnv.irs then
|
||||
self.keyPressing[5]=false
|
||||
end
|
||||
end
|
||||
end
|
||||
function Player:act_hardDrop()
|
||||
@@ -300,6 +334,10 @@ function Player:act_hardDrop()
|
||||
if self.lastPiece.autoLock and self.frameRun-self.lastPiece.frame<ENV.dropcut then
|
||||
SFX.play('drop_cancel',.3)
|
||||
else
|
||||
if self.bufferedIRS then
|
||||
-- If the player drops quicker than their IRS cut delay, make sure IRS still resolves.
|
||||
self:resolveIRS()
|
||||
end
|
||||
if self.curY>self.ghoY then
|
||||
self:createDropFX()
|
||||
self.curY=self.ghoY
|
||||
@@ -344,7 +382,10 @@ function Player:act_hold()
|
||||
if not self.control then return end
|
||||
if self.cur then
|
||||
if self:hold() then
|
||||
self.keyPressing[8]=false
|
||||
-- Disable held inputs if IHS is off
|
||||
if not self.gameEnv.ihs then
|
||||
self.keyPressing[8]=false
|
||||
end
|
||||
self:_triggerEvent('hook_hold')
|
||||
end
|
||||
end
|
||||
@@ -701,6 +742,40 @@ function Player:_triggerEvent(eventName)
|
||||
return true
|
||||
end
|
||||
end
|
||||
function Player:extraEvent(eventName,...)
|
||||
if not (self.gameEnv.extraEvent and self.gameEnv.extraEventHandler) then return end
|
||||
local list=self.gameEnv.extraEvent
|
||||
local eventID
|
||||
for i=1,#list do
|
||||
if list[i][1]==eventName then
|
||||
eventID=i
|
||||
break
|
||||
end
|
||||
end
|
||||
if not eventID then
|
||||
MES.new('warn',"Extra event '"..eventName.."' doesn't exist in this mode")
|
||||
return
|
||||
end
|
||||
|
||||
local SELF
|
||||
-- Trigger for all non-remote players
|
||||
for _,p in next,PLAYERS do
|
||||
if p.type~='remote' then
|
||||
if p.type=='human' then
|
||||
SELF=p
|
||||
end
|
||||
self.gameEnv.extraEventHandler[eventName](p,self,...)
|
||||
end
|
||||
end
|
||||
|
||||
ins(GAME.rep,SELF.frameRun)
|
||||
ins(GAME.rep,64+eventID)
|
||||
ins(GAME.rep,self.sid)
|
||||
local data={...}
|
||||
for i=1,#data do
|
||||
ins(GAME.rep,data[i])
|
||||
end
|
||||
end
|
||||
|
||||
function Player:getHolePos()-- Get a good garbage-line hole position
|
||||
if self.garbageBeneath==0 then
|
||||
@@ -833,34 +908,13 @@ function Player:ifoverlap(bk,x,y)
|
||||
end
|
||||
end
|
||||
end
|
||||
function Player:attack(R,send,time,line,fromStream)
|
||||
if GAME.net then
|
||||
if self.type=='human' then-- Local player attack others
|
||||
ins(GAME.rep,self.frameRun)
|
||||
ins(GAME.rep,
|
||||
R.sid+
|
||||
send*0x100+
|
||||
time*0x10000+
|
||||
line*0x100000000+
|
||||
0x2000000000000
|
||||
)
|
||||
self:createBeam(R,send)
|
||||
end
|
||||
if fromStream and R.type=='human' then-- Local player receiving lines
|
||||
ins(GAME.rep,R.frameRun)
|
||||
ins(GAME.rep,
|
||||
self.sid+
|
||||
send*0x100+
|
||||
time*0x10000+
|
||||
line*0x100000000+
|
||||
0x1000000000000
|
||||
)
|
||||
R:receive(self,send,time,line)
|
||||
end
|
||||
else
|
||||
R:receive(self,send,time,line)
|
||||
self:createBeam(R,send)
|
||||
end
|
||||
function Player:attack(R,send,time,line)
|
||||
self:extraEvent('attack',R.sid,send,time,line)
|
||||
end
|
||||
function Player:beAttacked(P2,sid,send,time,line)
|
||||
if self==P2 or self.sid~=sid then return end
|
||||
self:receive(P2,send,time,line)
|
||||
P2:createBeam(self,send)
|
||||
end
|
||||
function Player:receive(A,send,time,line)
|
||||
self.lastRecv=A
|
||||
@@ -1151,34 +1205,67 @@ function Player:resetBlock()-- Reset Block's position and execute I*S
|
||||
self.curY=y
|
||||
self.minY=y+sc[1]
|
||||
|
||||
local ENV=self.gameEnv
|
||||
|
||||
-- In the game settings, there are user-set control flags for irs,irs,ims
|
||||
-- These control in what way the user can buffer their rotate/hold/move inputs.
|
||||
-- (If enabled, they may hold these inputs from the previous piece instead of just Entry Delay)
|
||||
|
||||
-- And mode-set flags for logicalIRS,logicalIHS,logicalIMS
|
||||
-- These control whether IRS/IHS/IMS are effective in modifying what you can do.
|
||||
-- (For instance, changing your piece's spawn position in 20g, or saving you from a death).
|
||||
-- If logical IRS is disabled, the player may still IRS, but it will just buffer their input,
|
||||
-- not actually allowing them to survive in a way they could not without.
|
||||
|
||||
local pressing=self.keyPressing
|
||||
-- IMS
|
||||
if self.gameEnv.ims and (pressing[1] and self.movDir==-1 or pressing[2] and self.movDir==1) and self.moving>=self.gameEnv.das then
|
||||
local x=self.curX+self.movDir
|
||||
if not self:ifoverlap(C.bk,x,y) then
|
||||
self.curX=x
|
||||
-- IMS is enabled only when logicalIMS is enabled, because otherwise, it's just faster DAS.
|
||||
if ENV.logicalIMS and (pressing[1] and self.movDir==-1 or pressing[2] and self.movDir==1) and self.moving>=self.gameEnv.das then
|
||||
-- To avoid a top-out
|
||||
if self:ifoverlap(C.bk,self.curX,self.curY) then
|
||||
-- Always perform the shift, since you're topped out anyway
|
||||
self.curX=self.curX+self.movDir
|
||||
elseif ENV.wait>0 and ENV.ims then
|
||||
-- Otherwise, only check IMS if it's enabled and you're in a mode with entry delay (20g)
|
||||
local x=self.curX+self.movDir
|
||||
if not self:ifoverlap(C.bk,x,y) then
|
||||
self.curX=x
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- IRS
|
||||
if self.gameEnv.irs then
|
||||
if not ENV.logicalIRS then
|
||||
-- If logical IRS is disabled, all IRS inputs will be buffered to prevent survival.
|
||||
self.bufferedIRS=true
|
||||
self.bufferedDelay=0
|
||||
if ENV.wait==0 then
|
||||
self.bufferedDelay=ENV.irscut
|
||||
end
|
||||
elseif ENV.wait==0 and ENV.irscut>0 and not self:ifoverlap(C.bk,self.curX,self.curY) then
|
||||
-- If IRS cut delay is enabled and we aren't currently dying, buffer the input instead.
|
||||
self.bufferedIRS=true
|
||||
self.bufferedDelay=ENV.irscut
|
||||
else
|
||||
-- If we're currently dying or in an entry-delay mode (20g), perform the rotation right away.
|
||||
if pressing[5] then
|
||||
self:spin(2,true)
|
||||
self:act_rot180()
|
||||
elseif pressing[3] then
|
||||
if pressing[4] then
|
||||
self:spin(2,true)
|
||||
self:act_rot180()
|
||||
else
|
||||
self:spin(1,true)
|
||||
self:act_rotRight()
|
||||
end
|
||||
elseif pressing[4] then
|
||||
self:spin(3,true)
|
||||
self:act_rotLeft()
|
||||
end
|
||||
end
|
||||
-- Disable held inputs if IRS is off
|
||||
if not ENV.irs then
|
||||
pressing[3],pressing[4],pressing[5]=false,false,false
|
||||
end
|
||||
|
||||
-- DAS cut
|
||||
if self.gameEnv.dascut>0 then
|
||||
self.moving=self.moving-(self.moving>0 and 1 or -1)*self.gameEnv.dascut
|
||||
if ENV.dascut>0 then
|
||||
self.moving=self.moving-(self.moving>0 and 1 or -1)*ENV.dascut
|
||||
end
|
||||
|
||||
-- Spawn SFX
|
||||
@@ -1498,9 +1585,29 @@ function Player:_popNext(ifhold)-- Pop nextQueue to hand
|
||||
local pressing=self.keyPressing
|
||||
|
||||
-- IHS
|
||||
if not ifhold and pressing[8] and ENV.ihs and self.holdTime>0 then
|
||||
self:hold(true)
|
||||
pressing[8]=false
|
||||
if not ifhold and pressing[8] and self.holdTime>0 then
|
||||
if not ENV.logicalIHS then
|
||||
-- If logical IHS is disabled, all IHS inputs will be buffered to prevent survival.
|
||||
self.bufferedIRS=true
|
||||
self.bufferedIHS=true
|
||||
self.bufferedDelay=0
|
||||
if ENV.wait==0 then
|
||||
self.bufferedDelay=ENV.irscut
|
||||
end
|
||||
elseif ENV.wait==0 and ENV.irscut>0 and not self:willDieWith(self.cur) then
|
||||
-- If IRS cut delay is enabled and we're not currently dying, buffer the input instead.
|
||||
self.bufferedIRS=true
|
||||
self.bufferedIHS=true
|
||||
self.bufferedDelay=ENV.irscut
|
||||
self:resetBlock()
|
||||
else
|
||||
-- If we're currently dying or in an entry-delay mode (20g), perform the hold immediately.
|
||||
self:hold(true)
|
||||
end
|
||||
-- Disable held inputs if IHS is off
|
||||
if not ENV.ihs then
|
||||
pressing[8]=false
|
||||
end
|
||||
else
|
||||
self:resetBlock()
|
||||
end
|
||||
@@ -1814,7 +1921,7 @@ do
|
||||
end
|
||||
end
|
||||
|
||||
local yomi = ""
|
||||
local yomi=''
|
||||
|
||||
piece.spin,piece.mini=dospin,false
|
||||
piece.pc,piece.hpc=false,false
|
||||
@@ -1825,7 +1932,7 @@ do
|
||||
cscore=(spinSCR[C.name] or spinSCR[8])[cc]
|
||||
if self.b2b>800 then
|
||||
self:showText(text.b3b..text.block[C.name]..text.spin..text.clear[cc],0,-30,35,'stretch')
|
||||
yomi = yomi..text.b3b..text.block[C.name]..text.spin..text.clear[cc]
|
||||
yomi=yomi..text.b3b..text.block[C.name]..text.spin..text.clear[cc]
|
||||
atk=b2bATK[cc]+cc*.5
|
||||
exblock=exblock+1
|
||||
cscore=cscore*2
|
||||
@@ -1835,7 +1942,7 @@ do
|
||||
end
|
||||
elseif self.b2b>=50 then
|
||||
self:showText(text.b2b..text.block[C.name]..text.spin..text.clear[cc],0,-30,35,'spin')
|
||||
yomi = yomi..text.b2b..text.block[C.name]..text.spin..text.clear[cc]
|
||||
yomi=yomi..text.b2b..text.block[C.name]..text.spin..text.clear[cc]
|
||||
atk=b2bATK[cc]
|
||||
cscore=cscore*1.2
|
||||
Stat.b2b=Stat.b2b+1
|
||||
@@ -1844,13 +1951,13 @@ do
|
||||
end
|
||||
else
|
||||
self:showText(text.block[C.name]..text.spin..text.clear[cc],0,-30,45,'spin')
|
||||
yomi = yomi..text.block[C.name]..text.spin..text.clear[cc]
|
||||
yomi=yomi..text.block[C.name]..text.spin..text.clear[cc]
|
||||
atk=2*cc
|
||||
end
|
||||
sendTime=20+atk*20
|
||||
if mini then
|
||||
self:showText(text.mini,0,-80,35,'appear')
|
||||
yomi = text.mini..' '..yomi
|
||||
yomi=text.mini..' '..yomi
|
||||
atk=atk*.25
|
||||
sendTime=sendTime+60
|
||||
cscore=cscore*.5
|
||||
@@ -1871,7 +1978,7 @@ do
|
||||
cscore=clearSCR[cc]
|
||||
if self.b2b>800 then
|
||||
self:showText(text.b3b..text.clear[cc],0,-30,50,'fly')
|
||||
yomi = text.b3b..text.clear[cc]..yomi
|
||||
yomi=text.b3b..text.clear[cc]..yomi
|
||||
atk=4*cc-10
|
||||
sendTime=100
|
||||
exblock=exblock+1
|
||||
@@ -1882,7 +1989,7 @@ do
|
||||
end
|
||||
elseif self.b2b>=50 then
|
||||
self:showText(text.b2b..text.clear[cc],0,-30,50,'drive')
|
||||
yomi = text.b2b..text.clear[cc]..yomi
|
||||
yomi=text.b2b..text.clear[cc]..yomi
|
||||
sendTime=80
|
||||
atk=3*cc-7
|
||||
cscore=cscore*1.3
|
||||
@@ -1892,7 +1999,7 @@ do
|
||||
end
|
||||
else
|
||||
self:showText(text.clear[cc],0,-30,70,'stretch')
|
||||
yomi = text.clear[cc]..yomi
|
||||
yomi=text.clear[cc]..yomi
|
||||
sendTime=60
|
||||
atk=2*cc-4
|
||||
end
|
||||
@@ -1900,7 +2007,7 @@ do
|
||||
piece.special=true
|
||||
else
|
||||
self:showText(text.clear[cc],0,-30,35,'appear',(8-cc)*.3)
|
||||
yomi = text.clear[cc]..yomi
|
||||
yomi=text.clear[cc]..yomi
|
||||
atk=cc-.5
|
||||
sendTime=20+floor(atk*20)
|
||||
cscore=cscore+clearSCR[cc]
|
||||
@@ -1919,7 +2026,7 @@ do
|
||||
atk=atk+1
|
||||
end
|
||||
self:showText(text.cmb[min(cmb,21)],0,25,15+min(cmb,15)*5,cmb<10 and 'appear' or 'flicker')
|
||||
yomi = yomi..' '..text.cmb[min(cmb,21)]
|
||||
yomi=yomi..' '..text.cmb[min(cmb,21)]
|
||||
cscore=cscore+min(50*cmb,500)*(2*cc-1)
|
||||
end
|
||||
|
||||
@@ -2447,6 +2554,28 @@ local function _updateFX(P,dt)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Player:resolveIRS()
|
||||
if self.bufferedIHS then
|
||||
self:hold(true)
|
||||
self.bufferedIHS=false
|
||||
end
|
||||
|
||||
self.bufferedIRS=false
|
||||
local pressing=self.keyPressing
|
||||
if pressing[5] then
|
||||
self:act_rot180()
|
||||
elseif pressing[3] then
|
||||
if pressing[4] then
|
||||
self:act_rot180()
|
||||
else
|
||||
self:act_rotRight()
|
||||
end
|
||||
elseif pressing[4] then
|
||||
self:act_rotLeft()
|
||||
end
|
||||
end
|
||||
|
||||
local function update_alive(P,dt)
|
||||
local ENV=P.gameEnv
|
||||
|
||||
@@ -2505,6 +2634,18 @@ local function update_alive(P,dt)
|
||||
end
|
||||
end
|
||||
|
||||
-- Buffer IRS after IRS cut delay has elapsed.
|
||||
-- The purpose of this is to allow the player to release their rotate key during the IRS cut delay,
|
||||
-- which will allow them to avoid accidentally using IRS.
|
||||
if P.bufferedDelay then
|
||||
P.bufferedDelay=P.bufferedDelay-1
|
||||
if P.bufferedDelay<=0 then
|
||||
if P.bufferedIRS then
|
||||
P:resolveIRS()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Moving pressed
|
||||
if P.movDir~=0 then
|
||||
local das,arr=ENV.das,ENV.arr
|
||||
@@ -2701,44 +2842,32 @@ local function update_alive(P,dt)
|
||||
end
|
||||
local function update_streaming(P)
|
||||
local eventTime=P.stream[P.streamProgress]
|
||||
while eventTime and P.frameRun==eventTime do
|
||||
while eventTime and P.frameRun==eventTime or eventTime==0 do
|
||||
local event=P.stream[P.streamProgress+1]
|
||||
if event==0 then-- Just wait
|
||||
elseif event<=32 then-- Press key
|
||||
P:pressKey(event)
|
||||
elseif event<=64 then-- Release key
|
||||
P:releaseKey(event-32)
|
||||
elseif event>0x2000000000000 then-- Sending lines
|
||||
local sid=event%0x100
|
||||
local amount=floor(event/0x100)%0x100
|
||||
local time=floor(event/0x10000)%0x10000
|
||||
local line=floor(event/0x100000000)%0x10000
|
||||
for _,p in next,PLY_ALIVE do
|
||||
if p.sid==sid then
|
||||
P.netAtk=P.netAtk+amount
|
||||
if P.netAtk~=P.stat.send then-- He cheated or just desynchronized to death
|
||||
MES.new('warn',"#"..P.uid.." desynchronized")
|
||||
NET.player_finish({reason='desync'})
|
||||
P:lose(true)
|
||||
return
|
||||
end
|
||||
P:attack(p,amount,time,line,true)
|
||||
P:createBeam(p,amount)
|
||||
elseif event<=128 then-- Extra Event
|
||||
local eventName=P.gameEnv.extraEvent[event-64][1]
|
||||
local eventParamCount=P.gameEnv.extraEvent[event-64][2]
|
||||
local sourceSid=P.stream[P.streamProgress+2]
|
||||
local paramList={}
|
||||
for i=1,eventParamCount do
|
||||
ins(paramList,P.stream[P.streamProgress+2+i])
|
||||
end
|
||||
P.streamProgress=P.streamProgress+eventParamCount+1
|
||||
|
||||
local SRC
|
||||
for _,p in next,PLAYERS do
|
||||
if p.sid==sourceSid then
|
||||
SRC=p
|
||||
break
|
||||
end
|
||||
end
|
||||
elseif event>0x1000000000000 then-- Receiving lines
|
||||
local sid=event%0x100
|
||||
for _,p in next,PLY_ALIVE do
|
||||
if p.sid==sid then
|
||||
P:receive(
|
||||
p,
|
||||
floor(event/0x100)%0x100,-- amount
|
||||
floor(event/0x10000)%0x10000,-- time
|
||||
floor(event/0x100000000)%0x10000-- line
|
||||
)
|
||||
break
|
||||
end
|
||||
if SRC then
|
||||
P.gameEnv.extraEventHandler[eventName](P,SRC,unpack(paramList))
|
||||
end
|
||||
end
|
||||
P.streamProgress=P.streamProgress+2
|
||||
@@ -2801,7 +2930,7 @@ function Player:update(dt)
|
||||
end
|
||||
while self.trigFrame>=1 do
|
||||
if self.streamProgress then
|
||||
local frameDelta-- Time between now and end of stream
|
||||
local dataDelta -- How much data wating to be process
|
||||
if self.type=='remote' then
|
||||
if self.loseTimer then
|
||||
self.loseTimer=self.loseTimer-1
|
||||
@@ -2810,25 +2939,26 @@ function Player:update(dt)
|
||||
self:lose(true)
|
||||
end
|
||||
end
|
||||
frameDelta=(self.stream[#self.stream-1] or 0)-self.frameRun
|
||||
if frameDelta==0 then frameDelta=nil end
|
||||
dataDelta=#self.stream-self.streamProgress
|
||||
else
|
||||
frameDelta=0
|
||||
dataDelta=1
|
||||
end
|
||||
if frameDelta then
|
||||
if dataDelta>0 then
|
||||
for _=1,
|
||||
self.loseTimer and min(frameDelta,
|
||||
-- Speed up to finish
|
||||
self.loseTimer and min(dataDelta,
|
||||
self.loseTimer>16 and 2 or
|
||||
self.loseTimer>6.2 and 12 or
|
||||
self.loseTimer>2.6 and 260 or
|
||||
2600
|
||||
) or
|
||||
frameDelta<26 and 1 or
|
||||
frameDelta<50 and 2 or
|
||||
frameDelta<80 and 3 or
|
||||
frameDelta<120 and 5 or
|
||||
frameDelta<160 and 7 or
|
||||
frameDelta<200 and 10 or
|
||||
-- Chasing faster when slower
|
||||
dataDelta<26 and 1 or
|
||||
dataDelta<42 and 2 or
|
||||
dataDelta<62 and 3 or
|
||||
dataDelta<70.23 and 5 or
|
||||
dataDelta<94.2 and 7 or
|
||||
dataDelta<126 and 10 or
|
||||
20
|
||||
do
|
||||
update_streaming(self)
|
||||
@@ -2877,7 +3007,7 @@ function Player:revive()
|
||||
SFX.play('emit')
|
||||
end
|
||||
function Player:torikanEnd(requiredTime)
|
||||
if self.stat.time < requiredTime then
|
||||
if self.stat.time<requiredTime then
|
||||
return false
|
||||
end
|
||||
self:_die()
|
||||
|
||||
@@ -120,20 +120,17 @@ local seqGenerators={
|
||||
-- Pick a mino from pool
|
||||
local tryTime=0
|
||||
local r
|
||||
repeat-- ::REPEAT_pickAgain::
|
||||
local pickAgain
|
||||
repeat
|
||||
r=_poolPick()-- Random mino-index in pool
|
||||
local duplicated
|
||||
for i=1,len do
|
||||
if r==history[i] then
|
||||
tryTime=tryTime+1
|
||||
if tryTime<hisLen then
|
||||
pickAgain=true
|
||||
break-- goto REPEAT_pickAgain
|
||||
end
|
||||
duplicated=true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not pickAgain then break end
|
||||
until true
|
||||
tryTime=tryTime+1
|
||||
until not duplicated or tryTime>hisLen
|
||||
|
||||
-- Give mino to player & update history
|
||||
if history[1]~=0 then
|
||||
|
||||
@@ -29,6 +29,7 @@ function scene.enter()
|
||||
slide=true
|
||||
pathVis=true
|
||||
revKB=false
|
||||
DiscordRPC.update("Playing 15-Puzzle")
|
||||
end
|
||||
|
||||
local function moveU(x,y)
|
||||
|
||||
@@ -234,6 +234,7 @@ function scene.enter()
|
||||
tapControl=false
|
||||
startTime=0
|
||||
reset()
|
||||
DiscordRPC.update("Playing 2048")
|
||||
end
|
||||
|
||||
function scene.mouseDown(x,y,k)
|
||||
|
||||
@@ -40,6 +40,7 @@ function scene.enter()
|
||||
startTime=0
|
||||
time=0
|
||||
state=0
|
||||
DiscordRPC.update("Spamming keyboard")
|
||||
end
|
||||
|
||||
function scene.keyDown(key,isRep)
|
||||
|
||||
@@ -100,6 +100,7 @@ function scene.enter()
|
||||
restart()
|
||||
BGM.play('truth')
|
||||
BG.set('rainbow')
|
||||
DiscordRPC.update("Playing Ultimate Tic-Tac-Toe")
|
||||
end
|
||||
|
||||
function scene.mouseMove(x,y)
|
||||
|
||||
@@ -360,6 +360,7 @@ function scene.enter()
|
||||
drawing=false
|
||||
numScale=1
|
||||
BGM.play('truth')
|
||||
DiscordRPC.update("Playing Arithmetic")
|
||||
end
|
||||
|
||||
function scene.keyDown(key,isRep)
|
||||
|
||||
@@ -28,6 +28,7 @@ function scene.enter()
|
||||
BG.set('none')
|
||||
BGM.stop()
|
||||
reg,val,sym=false,"0",false
|
||||
DiscordRPC.update("Calculating something")
|
||||
end
|
||||
function scene.leave()
|
||||
BGM.play()
|
||||
|
||||
@@ -15,6 +15,7 @@ function scene.enter()
|
||||
ex,ey=626,260
|
||||
BG.set('matrix')
|
||||
BGM.play('hang out')
|
||||
DiscordRPC.update("Shooting cannon balls")
|
||||
end
|
||||
|
||||
function scene.keyDown(key,isRep)
|
||||
|
||||
@@ -1034,6 +1034,7 @@ userG.the_key=first_key
|
||||
function scene.enter()
|
||||
WIDGET.focus(inputBox)
|
||||
BG.set('none')
|
||||
DiscordRPC.update("Hacking the system")
|
||||
end
|
||||
|
||||
function scene.wheelMoved(_,y)
|
||||
|
||||
@@ -59,6 +59,7 @@ function scene.enter()
|
||||
gc.setLineJoin('bevel')
|
||||
BGM.play('push')
|
||||
BG.set('none')
|
||||
DiscordRPC.update("Playing Cubefield")
|
||||
end
|
||||
|
||||
function scene.touchDown(x)
|
||||
|
||||
@@ -38,6 +38,7 @@ function scene.enter()
|
||||
state='menu'
|
||||
BGM.play('hang out')
|
||||
BG.set('space')
|
||||
DiscordRPC.update("Playing Dropper")
|
||||
end
|
||||
|
||||
function scene.keyDown(key,isRep)
|
||||
|
||||
@@ -201,6 +201,7 @@ function scene.enter()
|
||||
reset()
|
||||
BG.set('fixColor',.26,.26,.26)
|
||||
BGM.play(bgm)
|
||||
DiscordRPC.update("Avoiding touching white tiles")
|
||||
end
|
||||
|
||||
local function touch(n)
|
||||
|
||||
@@ -249,6 +249,7 @@ function scene.enter()
|
||||
invis=false
|
||||
newGame()
|
||||
BGM.play('truth')
|
||||
DiscordRPC.update("Playing Link")
|
||||
end
|
||||
|
||||
function scene.keyDown(key,isRep)
|
||||
|
||||
@@ -109,6 +109,7 @@ function scene.enter()
|
||||
BG.set('fixColor',.26,.62,.26)
|
||||
_newGame()
|
||||
selected=false
|
||||
DiscordRPC.update("Playing Mahjong")
|
||||
end
|
||||
|
||||
function scene.mouseMove(x,y)
|
||||
|
||||
@@ -36,6 +36,7 @@ function scene.enter()
|
||||
input=''
|
||||
showNum='memoriZe'
|
||||
BGM.play('reason')
|
||||
DiscordRPC.update("Playing memoriZe")
|
||||
end
|
||||
|
||||
function scene.keyDown(key,isRep)
|
||||
|
||||
@@ -95,6 +95,7 @@ function scene.enter()
|
||||
generateVKey()
|
||||
_notHoldCS()
|
||||
_showVirtualKey(MOBILE)
|
||||
DiscordRPC.update("Playing music")
|
||||
end
|
||||
|
||||
function scene.leave()
|
||||
|
||||
@@ -40,6 +40,7 @@ function scene.enter()
|
||||
end
|
||||
BG.set('none')
|
||||
BGM.play('dream')
|
||||
DiscordRPC.update("Playing polyforge")
|
||||
end
|
||||
|
||||
function scene.keyDown(key,isRep)
|
||||
|
||||
@@ -35,6 +35,7 @@ function scene.enter()
|
||||
vy=0,
|
||||
y0=false,
|
||||
}
|
||||
DiscordRPC.update("Playing Pong")
|
||||
end
|
||||
|
||||
local function start()
|
||||
|
||||
@@ -18,6 +18,7 @@ end
|
||||
function scene.enter()
|
||||
reset()
|
||||
BG.set('none')
|
||||
DiscordRPC.update("Playing Reflect")
|
||||
end
|
||||
|
||||
function scene.keyDown(key,isRep)
|
||||
|
||||
@@ -25,6 +25,7 @@ function scene.enter()
|
||||
mistake=0
|
||||
state=0
|
||||
progress=0
|
||||
DiscordRPC.update("Playing Schulte Grid")
|
||||
end
|
||||
|
||||
local function newBoard()
|
||||
|
||||
@@ -15,6 +15,10 @@ end)
|
||||
|
||||
local scene={}
|
||||
|
||||
function scene.enter()
|
||||
DiscordRPC.update("Playing a non-sense thing")
|
||||
end
|
||||
|
||||
function scene.keyDown(key,isRep)
|
||||
if isRep then return end
|
||||
if key=='space' or key=='return' then
|
||||
|
||||