2018-09-13 01:10:00 +08:00
|
|
|
class Controller{
|
2018-10-06 01:03:59 +08:00
|
|
|
constructor(selectedSong, songData, autoPlayEnabled, multiplayer, touchEnabled){
|
2018-09-13 01:10:00 +08:00
|
|
|
this.selectedSong = selectedSong
|
|
|
|
this.songData = songData
|
|
|
|
this.autoPlayEnabled = autoPlayEnabled
|
2020-03-09 20:36:57 +08:00
|
|
|
this.saveScore = !autoPlayEnabled
|
2018-09-13 01:10:00 +08:00
|
|
|
this.multiplayer = multiplayer
|
2018-10-06 01:03:59 +08:00
|
|
|
this.touchEnabled = touchEnabled
|
2020-03-16 20:22:16 +08:00
|
|
|
if(multiplayer === 2){
|
|
|
|
this.snd = p2.player === 2 ? "_p1" : "_p2"
|
2020-04-04 21:48:58 +08:00
|
|
|
this.don = p2.don || defaultDon
|
2020-03-16 20:22:16 +08:00
|
|
|
}else{
|
|
|
|
this.snd = multiplayer ? "_p" + p2.player : ""
|
2020-04-04 21:48:58 +08:00
|
|
|
this.don = account.loggedIn ? account.don : defaultDon
|
|
|
|
}
|
|
|
|
if(this.snd === "_p2" && this.objEqual(defaultDon, this.don)){
|
|
|
|
this.don = {
|
|
|
|
body_fill: defaultDon.face_fill,
|
|
|
|
face_fill: defaultDon.body_fill
|
|
|
|
}
|
2020-03-16 20:22:16 +08:00
|
|
|
}
|
2018-09-13 01:10:00 +08:00
|
|
|
|
2019-11-28 14:04:40 +08:00
|
|
|
this.calibrationMode = selectedSong.folder === "calibration"
|
|
|
|
this.audioLatency = 0
|
|
|
|
this.videoLatency = 0
|
|
|
|
if(!this.calibrationMode){
|
|
|
|
var latency = settings.getItem("latency")
|
|
|
|
if(!autoPlayEnabled){
|
|
|
|
this.audioLatency = Math.round(latency.audio) || 0
|
|
|
|
}
|
|
|
|
this.videoLatency = Math.round(latency.video) || 0 + this.audioLatency
|
|
|
|
}
|
2019-01-16 20:33:42 +08:00
|
|
|
if(this.multiplayer !== 2){
|
|
|
|
loader.changePage("game", false)
|
|
|
|
}
|
|
|
|
|
2018-10-11 06:13:24 +08:00
|
|
|
if(selectedSong.type === "tja"){
|
2019-11-04 22:20:44 +08:00
|
|
|
this.parsedSongData = new ParseTja(songData, selectedSong.difficulty, selectedSong.stars, selectedSong.offset)
|
2018-10-11 06:13:24 +08:00
|
|
|
}else{
|
2019-11-04 22:20:44 +08:00
|
|
|
this.parsedSongData = new ParseOsu(songData, selectedSong.difficulty, selectedSong.stars, selectedSong.offset)
|
2018-10-11 06:13:24 +08:00
|
|
|
}
|
|
|
|
this.offset = this.parsedSongData.soundOffset
|
2020-10-31 19:47:42 +08:00
|
|
|
|
2019-12-25 03:37:10 +08:00
|
|
|
var maxCombo = this.parsedSongData.circles.filter(circle => ["don", "ka", "daiDon", "daiKa"].indexOf(circle.type) > -1 && (!circle.branch || circle.branch.name == "master")).length
|
|
|
|
if (maxCombo >= 50) {
|
2020-10-31 19:47:42 +08:00
|
|
|
var comboVoices = ["v_combo_50"].concat(Array.from(Array(Math.min(50, Math.floor(maxCombo / 100))), (d, i) => "v_combo_" + ((i + 1) * 100)))
|
2019-12-25 03:37:10 +08:00
|
|
|
var promises = []
|
2020-10-31 19:47:42 +08:00
|
|
|
|
2019-12-25 03:37:10 +08:00
|
|
|
comboVoices.forEach(name => {
|
|
|
|
if (!assets.sounds[name + "_p1"]) {
|
2020-11-09 05:30:56 +08:00
|
|
|
promises.push(loader.loadSound(name + ".ogg", snd.sfxGain).then(sound => {
|
2019-12-25 03:37:10 +08:00
|
|
|
assets.sounds[name + "_p1"] = assets.sounds[name].copy(snd.sfxGainL)
|
|
|
|
assets.sounds[name + "_p2"] = assets.sounds[name].copy(snd.sfxGainR)
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
})
|
2020-10-31 19:47:42 +08:00
|
|
|
|
2020-02-22 21:13:04 +08:00
|
|
|
Promise.all(promises)
|
2019-12-25 03:37:10 +08:00
|
|
|
}
|
2018-09-13 01:10:00 +08:00
|
|
|
|
2019-11-28 14:04:40 +08:00
|
|
|
if(this.calibrationMode){
|
|
|
|
this.volume = 1
|
|
|
|
}else{
|
|
|
|
assets.songs.forEach(song => {
|
|
|
|
if(song.id == this.selectedSong.folder){
|
|
|
|
this.mainAsset = song.sound
|
|
|
|
this.volume = song.volume || 1
|
2020-03-31 21:44:54 +08:00
|
|
|
if(!multiplayer && (!this.touchEnabled || this.autoPlayEnabled) && settings.getItem("showLyrics")){
|
2020-03-31 20:50:27 +08:00
|
|
|
if(song.lyricsData){
|
|
|
|
var lyricsDiv = document.getElementById("song-lyrics")
|
|
|
|
this.lyrics = new Lyrics(song.lyricsData, selectedSong.offset, lyricsDiv)
|
|
|
|
}else if(this.parsedSongData.lyrics){
|
|
|
|
var lyricsDiv = document.getElementById("song-lyrics")
|
|
|
|
this.lyrics = new Lyrics(this.parsedSongData.lyrics, selectedSong.offset, lyricsDiv, true)
|
|
|
|
}
|
2020-03-30 14:50:34 +08:00
|
|
|
}
|
2019-11-28 14:04:40 +08:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2015-07-17 16:22:46 +08:00
|
|
|
|
2018-09-13 01:10:00 +08:00
|
|
|
this.game = new Game(this, this.selectedSong, this.parsedSongData)
|
2018-11-24 00:53:29 +08:00
|
|
|
this.view = new View(this)
|
2018-09-13 01:10:00 +08:00
|
|
|
this.mekadon = new Mekadon(this, this.game)
|
2019-04-17 02:06:41 +08:00
|
|
|
this.keyboard = new GameInput(this)
|
2020-02-29 23:56:59 +08:00
|
|
|
if(!autoPlayEnabled && this.multiplayer !== 2){
|
|
|
|
this.easierBigNotes = settings.getItem("easierBigNotes") || this.keyboard.keyboard.TaikoForceLv5
|
|
|
|
}else{
|
|
|
|
this.easierBigNotes = false
|
|
|
|
}
|
2018-10-28 02:35:04 +08:00
|
|
|
|
2019-11-28 14:04:40 +08:00
|
|
|
this.drumSounds = settings.getItem("latency").drumSounds
|
2018-10-28 02:35:04 +08:00
|
|
|
this.playedSounds = {}
|
2018-09-13 01:10:00 +08:00
|
|
|
}
|
|
|
|
run(syncWith){
|
2019-01-25 09:42:05 +08:00
|
|
|
if(syncWith){
|
|
|
|
this.syncWith = syncWith
|
|
|
|
}
|
2019-03-16 05:34:48 +08:00
|
|
|
if(this.multiplayer !== 2){
|
|
|
|
snd.musicGain.setVolumeMul(this.volume)
|
|
|
|
}
|
2018-09-13 01:10:00 +08:00
|
|
|
this.game.run()
|
|
|
|
this.view.run()
|
2019-01-25 09:42:05 +08:00
|
|
|
if(this.multiplayer === 1){
|
|
|
|
syncWith.run(this)
|
2018-10-28 02:35:04 +08:00
|
|
|
syncWith.game.elapsedTime = this.game.elapsedTime
|
|
|
|
syncWith.game.startDate = this.game.startDate
|
2018-09-13 01:10:00 +08:00
|
|
|
}
|
2018-11-24 03:15:10 +08:00
|
|
|
requestAnimationFrame(() => {
|
|
|
|
this.startMainLoop()
|
|
|
|
if(!this.multiplayer){
|
|
|
|
debugObj.controller = this
|
|
|
|
if(debugObj.debug){
|
|
|
|
debugObj.debug.updateStatus()
|
|
|
|
}
|
2018-10-15 02:08:05 +08:00
|
|
|
}
|
2018-11-24 03:15:10 +08:00
|
|
|
})
|
2018-09-13 01:10:00 +08:00
|
|
|
}
|
|
|
|
startMainLoop(){
|
|
|
|
this.mainLoopRunning = true
|
2019-01-16 20:33:42 +08:00
|
|
|
this.gameLoop()
|
|
|
|
this.viewLoop()
|
2019-01-25 09:42:05 +08:00
|
|
|
if(this.multiplayer !== 2){
|
|
|
|
this.gameInterval = setInterval(this.gameLoop.bind(this), 1000 / 60)
|
Custom scripting, #song=, translations
- A song can be linked directly by adding "#song=<id>" to the url, replace `<id>` with the id in the database, after loading it jumps immediately jumps to the difficulty selection
- Added tutorial translations
- Fixed song preview not playing
- Use text fallback for the logo when there are no vectors
- Increased combo cache by 1 pixel
- A custom javascript file can be loaded from config.json by defining "custom_js" value
- Added lots of events to help writing custom js files: `version-link, title-screen, language-change, song-select, song-select-move, song-select-difficulty, song-select-back, about, about-link, tutorial, import-songs, import-songs-default, session, session-start, session-end, debug, load-song, load-song-player2, load-song-unfocused, load-song-cancel, load-song-error, game-start, key-events, p2-game-end, p2-disconnected, p2-abandoned, pause, unpause, pause-restart, pause-song-select, game-lag, scoresheet, scoresheet-player2`
- Event syntax example:
```js
addEventListener("game-start", event => {
console.log("game-start", event.detail)
})
```
2019-02-14 17:32:45 +08:00
|
|
|
pageEvents.send("game-start", {
|
|
|
|
selectedSong: this.selectedSong,
|
|
|
|
autoPlayEnabled: this.autoPlayEnabled,
|
|
|
|
multiplayer: this.multiplayer,
|
|
|
|
touchEnabled: this.touchEnabled
|
|
|
|
})
|
2019-01-25 09:42:05 +08:00
|
|
|
}
|
2018-09-13 01:10:00 +08:00
|
|
|
}
|
2018-09-15 22:34:53 +08:00
|
|
|
stopMainLoop(){
|
|
|
|
this.mainLoopRunning = false
|
2019-11-28 14:04:40 +08:00
|
|
|
if(this.game.mainAsset){
|
|
|
|
this.game.mainAsset.stop()
|
2019-03-06 05:48:30 +08:00
|
|
|
}
|
2019-01-25 09:42:05 +08:00
|
|
|
if(this.multiplayer !== 2){
|
|
|
|
clearInterval(this.gameInterval)
|
|
|
|
}
|
2018-09-15 22:34:53 +08:00
|
|
|
}
|
2019-01-16 20:33:42 +08:00
|
|
|
gameLoop(){
|
|
|
|
if(this.mainLoopRunning){
|
2019-01-25 09:42:05 +08:00
|
|
|
if(this.multiplayer === 1){
|
2019-01-16 20:33:42 +08:00
|
|
|
this.syncWith.game.elapsedTime = this.game.elapsedTime
|
|
|
|
this.syncWith.game.startDate = this.game.startDate
|
|
|
|
}
|
|
|
|
var ms = this.game.elapsedTime
|
|
|
|
|
2019-02-03 20:04:25 +08:00
|
|
|
if(this.game.musicFadeOut < 3){
|
|
|
|
this.keyboard.checkMenuKeys()
|
|
|
|
}
|
2019-11-28 14:04:40 +08:00
|
|
|
if(this.calibrationMode){
|
|
|
|
this.game.calibration()
|
|
|
|
}
|
2019-01-16 20:33:42 +08:00
|
|
|
if(!this.game.isPaused()){
|
|
|
|
this.keyboard.checkGameKeys()
|
|
|
|
|
|
|
|
if(ms < 0){
|
|
|
|
this.game.updateTime()
|
|
|
|
}else{
|
2019-11-28 14:04:40 +08:00
|
|
|
if(!this.calibrationMode){
|
|
|
|
this.game.update()
|
|
|
|
}
|
2019-01-16 20:33:42 +08:00
|
|
|
if(!this.mainLoopRunning){
|
|
|
|
return
|
|
|
|
}
|
|
|
|
this.game.playMainMusic()
|
|
|
|
}
|
|
|
|
}
|
2019-01-25 09:42:05 +08:00
|
|
|
if(this.multiplayer === 1){
|
|
|
|
this.syncWith.gameLoop()
|
|
|
|
}
|
2019-01-16 20:33:42 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
viewLoop(){
|
2018-09-13 01:10:00 +08:00
|
|
|
if(this.mainLoopRunning){
|
2018-09-18 06:37:59 +08:00
|
|
|
if(this.multiplayer !== 2){
|
2018-09-13 01:10:00 +08:00
|
|
|
requestAnimationFrame(() => {
|
2020-03-14 12:50:04 +08:00
|
|
|
var player = this.multiplayer ? p2.player : 1
|
|
|
|
if(player === 1){
|
|
|
|
this.viewLoop()
|
|
|
|
}
|
2019-01-25 09:42:05 +08:00
|
|
|
if(this.multiplayer === 1){
|
2019-01-16 20:33:42 +08:00
|
|
|
this.syncWith.viewLoop()
|
2018-09-13 01:10:00 +08:00
|
|
|
}
|
2020-03-14 12:50:04 +08:00
|
|
|
if(player === 2){
|
|
|
|
this.viewLoop()
|
|
|
|
}
|
2018-10-25 22:18:41 +08:00
|
|
|
if(this.scoresheet){
|
|
|
|
if(this.view.ctx){
|
|
|
|
this.view.ctx.save()
|
|
|
|
this.view.ctx.setTransform(1, 0, 0, 1, 0, 0)
|
|
|
|
}
|
|
|
|
this.scoresheet.redraw()
|
|
|
|
if(this.view.ctx){
|
|
|
|
this.view.ctx.restore()
|
|
|
|
}
|
|
|
|
}
|
2018-09-13 01:10:00 +08:00
|
|
|
})
|
|
|
|
}
|
2018-11-12 18:32:02 +08:00
|
|
|
this.view.refresh()
|
2018-09-13 01:10:00 +08:00
|
|
|
}
|
|
|
|
}
|
2018-09-18 06:37:59 +08:00
|
|
|
gameEnded(){
|
2018-09-13 01:10:00 +08:00
|
|
|
var score = this.getGlobalScore()
|
|
|
|
var vp
|
2020-03-05 23:58:49 +08:00
|
|
|
if(this.game.rules.clearReached(score.gauge)){
|
2018-10-25 22:18:41 +08:00
|
|
|
if(score.bad === 0){
|
|
|
|
vp = "fullcombo"
|
2019-12-25 02:10:40 +08:00
|
|
|
this.playSound("v_fullcombo", 1.350)
|
2018-10-25 22:18:41 +08:00
|
|
|
}else{
|
|
|
|
vp = "clear"
|
|
|
|
}
|
2018-09-15 22:34:53 +08:00
|
|
|
}else{
|
2018-09-13 01:10:00 +08:00
|
|
|
vp = "fail"
|
|
|
|
}
|
2019-02-04 17:14:42 +08:00
|
|
|
this.playSound("se_game" + vp)
|
2018-09-18 06:37:59 +08:00
|
|
|
}
|
|
|
|
displayResults(){
|
|
|
|
if(this.multiplayer !== 2){
|
2018-11-02 18:26:46 +08:00
|
|
|
this.scoresheet = new Scoresheet(this, this.getGlobalScore(), this.multiplayer, this.touchEnabled)
|
2018-09-18 06:37:59 +08:00
|
|
|
}
|
2018-09-13 01:10:00 +08:00
|
|
|
}
|
2018-10-25 22:18:41 +08:00
|
|
|
displayScore(score, notPlayed, bigNote){
|
|
|
|
this.view.displayScore(score, notPlayed, bigNote)
|
2018-09-13 01:10:00 +08:00
|
|
|
}
|
2020-03-16 21:42:36 +08:00
|
|
|
songSelection(fadeIn, showWarning){
|
2018-10-01 18:02:28 +08:00
|
|
|
if(!fadeIn){
|
|
|
|
this.clean()
|
|
|
|
}
|
2019-11-28 14:04:40 +08:00
|
|
|
if(this.calibrationMode){
|
|
|
|
new SettingsView(this.touchEnabled, false, null, "latency")
|
|
|
|
}else{
|
2020-03-16 21:42:36 +08:00
|
|
|
new SongSelect(false, fadeIn, this.touchEnabled, null, showWarning)
|
2019-11-28 14:04:40 +08:00
|
|
|
}
|
2018-09-13 01:10:00 +08:00
|
|
|
}
|
|
|
|
restartSong(){
|
2018-09-18 06:37:59 +08:00
|
|
|
this.clean()
|
|
|
|
if(this.multiplayer){
|
2018-12-13 17:18:52 +08:00
|
|
|
new LoadSong(this.selectedSong, false, true, this.touchEnabled)
|
2018-09-18 06:37:59 +08:00
|
|
|
}else{
|
2019-03-16 05:34:48 +08:00
|
|
|
new Promise(resolve => {
|
2019-11-28 14:04:40 +08:00
|
|
|
if(this.calibrationMode){
|
|
|
|
resolve()
|
|
|
|
}else{
|
|
|
|
var songObj = assets.songs.find(song => song.id === this.selectedSong.folder)
|
2020-03-31 20:50:27 +08:00
|
|
|
var promises = []
|
2019-11-28 14:04:40 +08:00
|
|
|
if(songObj.chart && songObj.chart !== "blank"){
|
2020-11-09 05:30:56 +08:00
|
|
|
var chart = songObj.chart
|
|
|
|
if(chart.separateDiff){
|
|
|
|
var chartDiff = this.selectedSong.difficulty
|
|
|
|
chart = chart[chartDiff]
|
|
|
|
}
|
|
|
|
promises.push(chart.read(this.selectedSong.type === "tja" ? "sjis" : undefined).then(data => {
|
2020-10-29 13:07:56 +08:00
|
|
|
this.songData = data.replace(/\0/g, "").split("\n")
|
2020-03-31 20:50:27 +08:00
|
|
|
return Promise.resolve()
|
|
|
|
}))
|
2019-03-16 05:34:48 +08:00
|
|
|
}
|
2020-03-31 20:50:27 +08:00
|
|
|
if(songObj.lyricsFile){
|
2020-11-04 08:12:46 +08:00
|
|
|
promises.push(songObj.lyricsFile.read().then(result => {
|
|
|
|
songObj.lyricsData = result
|
2020-10-29 13:07:56 +08:00
|
|
|
}, () => Promise.resolve()), songObj.lyricsFile.path)
|
2020-03-31 20:50:27 +08:00
|
|
|
}
|
|
|
|
Promise.all(promises).then(resolve)
|
2019-03-16 05:34:48 +08:00
|
|
|
}
|
|
|
|
}).then(() => {
|
|
|
|
var taikoGame = new Controller(this.selectedSong, this.songData, this.autoPlayEnabled, false, this.touchEnabled)
|
|
|
|
taikoGame.run()
|
|
|
|
})
|
2018-09-18 06:37:59 +08:00
|
|
|
}
|
2018-09-13 01:10:00 +08:00
|
|
|
}
|
2019-11-28 14:04:40 +08:00
|
|
|
playSound(id, time, noSnd){
|
|
|
|
if(!this.drumSounds && (id === "neiro_1_don" || id === "neiro_1_ka" || id === "se_don" || id === "se_ka")){
|
|
|
|
return
|
|
|
|
}
|
2018-12-13 17:18:52 +08:00
|
|
|
var ms = Date.now() + (time || 0) * 1000
|
2018-10-28 02:35:04 +08:00
|
|
|
if(!(id in this.playedSounds) || ms > this.playedSounds[id] + 30){
|
2019-11-28 14:04:40 +08:00
|
|
|
assets.sounds[id + (noSnd ? "" : this.snd)].play(time)
|
2018-10-28 02:35:04 +08:00
|
|
|
this.playedSounds[id] = ms
|
|
|
|
}
|
2018-10-03 17:48:18 +08:00
|
|
|
}
|
2019-11-28 14:04:40 +08:00
|
|
|
togglePause(forcePause, pauseMove, noSound){
|
2019-01-25 09:42:05 +08:00
|
|
|
if(this.multiplayer === 1){
|
2019-11-28 14:04:40 +08:00
|
|
|
this.syncWith.game.togglePause(forcePause, pauseMove, noSound)
|
2018-09-13 01:10:00 +08:00
|
|
|
}
|
2019-11-28 14:04:40 +08:00
|
|
|
this.game.togglePause(forcePause, pauseMove, noSound)
|
2018-09-13 01:10:00 +08:00
|
|
|
}
|
|
|
|
getKeys(){
|
|
|
|
return this.keyboard.getKeys()
|
|
|
|
}
|
2019-04-17 02:06:41 +08:00
|
|
|
setKey(pressed, name, ms){
|
|
|
|
return this.keyboard.setKey(pressed, name, ms)
|
2018-09-13 01:10:00 +08:00
|
|
|
}
|
|
|
|
getElapsedTime(){
|
2018-10-03 17:48:18 +08:00
|
|
|
return this.game.elapsedTime
|
2018-09-13 01:10:00 +08:00
|
|
|
}
|
|
|
|
getCircles(){
|
|
|
|
return this.game.getCircles()
|
|
|
|
}
|
|
|
|
getCurrentCircle(){
|
|
|
|
return this.game.getCurrentCircle()
|
|
|
|
}
|
2018-09-19 01:33:18 +08:00
|
|
|
isWaiting(key, type){
|
|
|
|
return this.keyboard.isWaiting(key, type)
|
2018-09-13 01:10:00 +08:00
|
|
|
}
|
|
|
|
waitForKeyup(key, type){
|
|
|
|
this.keyboard.waitForKeyup(key, type)
|
|
|
|
}
|
|
|
|
getKeyTime(){
|
|
|
|
return this.keyboard.getKeyTime()
|
|
|
|
}
|
|
|
|
getCombo(){
|
|
|
|
return this.game.getCombo()
|
|
|
|
}
|
|
|
|
getGlobalScore(){
|
|
|
|
return this.game.getGlobalScore()
|
|
|
|
}
|
|
|
|
autoPlay(circle){
|
|
|
|
if(this.multiplayer){
|
|
|
|
p2.play(circle, this.mekadon)
|
|
|
|
}else{
|
2019-02-24 20:04:14 +08:00
|
|
|
return this.mekadon.play(circle)
|
2018-09-13 01:10:00 +08:00
|
|
|
}
|
|
|
|
}
|
2020-04-04 21:48:58 +08:00
|
|
|
objEqual(a, b){
|
|
|
|
for(var i in a){
|
|
|
|
if(a[i] !== b[i]){
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
2018-09-18 06:37:59 +08:00
|
|
|
clean(){
|
2019-01-25 09:42:05 +08:00
|
|
|
if(this.multiplayer === 1){
|
2018-10-06 01:03:59 +08:00
|
|
|
this.syncWith.clean()
|
|
|
|
}
|
2018-09-18 06:37:59 +08:00
|
|
|
this.stopMainLoop()
|
|
|
|
this.keyboard.clean()
|
|
|
|
this.view.clean()
|
2019-03-16 05:34:48 +08:00
|
|
|
snd.buffer.loadSettings()
|
2018-09-18 06:37:59 +08:00
|
|
|
|
2018-10-15 04:14:58 +08:00
|
|
|
if(!this.multiplayer){
|
2018-10-15 02:08:05 +08:00
|
|
|
debugObj.controller = null
|
|
|
|
if(debugObj.debug){
|
|
|
|
debugObj.debug.updateStatus()
|
|
|
|
}
|
|
|
|
}
|
2020-03-30 14:50:34 +08:00
|
|
|
if(this.lyrics){
|
|
|
|
this.lyrics.clean()
|
|
|
|
}
|
2018-09-18 06:37:59 +08:00
|
|
|
}
|
2018-09-13 01:10:00 +08:00
|
|
|
}
|