├── docs
├── .nojekyll
├── _404.md
├── en
│ ├── _404.md
│ ├── _sidebar.md
│ └── README.md
├── _navbar.md
├── _sidebar.md
├── index.html
├── development
│ └── api
│ │ ├── README.md
│ │ ├── localization.md
│ │ └── card_deck.md
└── README.md
├── data
├── persistence
│ └── .gitkeep
└── .gitignore
├── mod
├── adapter
│ ├── mod.go
│ ├── player.go
│ ├── skill.go
│ ├── summon.go
│ ├── rule.go
│ ├── event.go
│ ├── single.go
│ └── card.go
├── definition
│ ├── summon.go
│ ├── damage.go
│ ├── context.go
│ ├── rule.go
│ ├── character.go
│ ├── mod.go
│ ├── event.go
│ ├── factory.go
│ ├── skill.go
│ ├── card.go
│ ├── language.go
│ ├── modifier.go
│ └── description.go
└── implement
│ ├── benchmark_test.go
│ ├── entity.go
│ ├── damage.go
│ ├── context.go
│ ├── event.go
│ ├── character.go
│ ├── rule.go
│ ├── skill.go
│ ├── mod.go
│ └── util.go
├── protocol
├── websocket
│ ├── service
│ │ ├── battle.go
│ │ └── match.go
│ ├── message
│ │ ├── initialize.go
│ │ └── action.go
│ ├── config
│ │ └── handler.go
│ └── engine.go
└── http
│ ├── config
│ ├── service.go
│ ├── engine.go
│ └── middleware.go
│ ├── service
│ ├── init.go
│ ├── config.go
│ ├── roominfo.go
│ └── localization.go
│ ├── message
│ ├── localization.go
│ ├── carddeck.go
│ └── player.go
│ ├── middleware
│ ├── uuid.go
│ ├── qps.go
│ ├── trace.go
│ ├── interdictor.go
│ └── authenticator.go
│ ├── engine.go
│ └── util
│ └── preprocess.go
├── entity
├── support.go
├── summon.go
├── model
│ ├── entity.go
│ ├── event.go
│ ├── skill.go
│ ├── interface.go
│ ├── card.go
│ ├── rule.go
│ └── cost.go
├── carddeck.go
├── benchmark_test.go
├── eventmap.go
├── character.go
└── player.go
├── model
├── adapter
│ └── adapter.go
├── modifier
│ ├── modifier.go
│ ├── definition
│ │ └── modifier.go
│ ├── context.go
│ └── benchmark_test.go
├── kv
│ ├── map.go
│ ├── pair.go
│ ├── simple.go
│ └── sync.go
├── context
│ ├── charge.go
│ ├── heal.go
│ ├── cost.go
│ ├── callback.go
│ └── damage.go
└── localization
│ └── language.go
├── enum
├── description.go
├── rule.go
├── modifier.go
├── equipment.go
├── skill.go
├── weapon.go
├── stage.go
├── character.go
├── language.go
├── affiliation.go
├── player.go
├── action.go
├── trigger.go
├── element.go
├── card.go
└── reaction.go
├── util
├── logger.go
├── password.go
├── benchmark_test.go
├── os.go
├── unit_test.go
└── generate.go
├── exec
├── cli
│ └── main.go
└── backend
│ └── main.go
├── .github
└── workflows
│ └── go.yml
├── license
├── .drone.yml
├── persistence
├── memcache.go
├── adapter.go
├── interface.go
├── sqlite.go
├── model.go
├── db.go
└── timing.go
├── go.mod
├── main.go
├── .gitignore
└── readme.md
/docs/.nojekyll:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/data/persistence/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/mod/adapter/mod.go:
--------------------------------------------------------------------------------
1 | package adapter
2 |
--------------------------------------------------------------------------------
/mod/adapter/player.go:
--------------------------------------------------------------------------------
1 | package adapter
2 |
--------------------------------------------------------------------------------
/mod/adapter/skill.go:
--------------------------------------------------------------------------------
1 | package adapter
2 |
--------------------------------------------------------------------------------
/mod/adapter/summon.go:
--------------------------------------------------------------------------------
1 | package adapter
2 |
--------------------------------------------------------------------------------
/protocol/websocket/service/battle.go:
--------------------------------------------------------------------------------
1 | package service
2 |
--------------------------------------------------------------------------------
/protocol/websocket/service/match.go:
--------------------------------------------------------------------------------
1 | package service
2 |
--------------------------------------------------------------------------------
/entity/support.go:
--------------------------------------------------------------------------------
1 | package entity
2 |
3 | type Support interface{}
4 |
--------------------------------------------------------------------------------
/entity/summon.go:
--------------------------------------------------------------------------------
1 | package entity
2 |
3 | type Summon interface {
4 | }
5 |
--------------------------------------------------------------------------------
/docs/_404.md:
--------------------------------------------------------------------------------
1 | # 你要找的页面走丢了
2 |
3 |
也有可能是作者创建了文件夹但是没有写东西
--------------------------------------------------------------------------------
/data/.gitignore:
--------------------------------------------------------------------------------
1 | # go-plugin的插件文件
2 | *.so
3 |
4 | # 可能的配置文件
5 | *.json
6 | *.xml
7 | *.yml
8 |
9 | # 持久化文件
10 | *.psc
11 | *.psc.quit
12 | *.db
13 |
--------------------------------------------------------------------------------
/model/adapter/adapter.go:
--------------------------------------------------------------------------------
1 | package adapter
2 |
3 | type Adapter[Origin any, Dest any] interface {
4 | Convert(source Origin) (success bool, result Dest)
5 | }
6 |
--------------------------------------------------------------------------------
/protocol/http/config/service.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | type ServiceConfig struct {
4 | MaxRooms uint64 `json:"max_rooms" yaml:"max_rooms" xml:"max_rooms"`
5 | }
6 |
--------------------------------------------------------------------------------
/entity/model/entity.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | // BaseEntity 实体的基本接口,包含被框架托管的必须方法
4 | type BaseEntity interface {
5 | TypeID() uint64
6 | InjectTypeID(id uint64)
7 | }
8 |
--------------------------------------------------------------------------------
/enum/description.go:
--------------------------------------------------------------------------------
1 | package enum
2 |
3 | type DescriptionType byte
4 |
5 | const (
6 | DescriptionTypePlainText DescriptionType = iota
7 | DescriptionTypeHtml
8 | )
9 |
--------------------------------------------------------------------------------
/docs/en/_404.md:
--------------------------------------------------------------------------------
1 | # The page you are looking for is missing
2 |
3 | It is also possible that the author created a folder but did not write anything
--------------------------------------------------------------------------------
/enum/rule.go:
--------------------------------------------------------------------------------
1 | package enum
2 |
3 | type RuleType byte
4 |
5 | const (
6 | RuleTypeNone RuleType = iota
7 | RuleTypeReactionCalculator
8 | RuleTypeVictorCalculator
9 | )
10 |
--------------------------------------------------------------------------------
/docs/_navbar.md:
--------------------------------------------------------------------------------
1 | - [pkg.go.dev](https://pkg.go.dev/github.com/sunist-c/genius-invokation-simulator-backend#section-readme)
2 | - Translations
3 | - [:cn: 中文](/)
4 | - [:uk: English](/en/)
--------------------------------------------------------------------------------
/mod/definition/summon.go:
--------------------------------------------------------------------------------
1 | package definition
2 |
3 | import "github.com/sunist-c/genius-invokation-simulator-backend/entity/model"
4 |
5 | type Summon interface {
6 | model.BaseEntity
7 | }
8 |
--------------------------------------------------------------------------------
/mod/definition/damage.go:
--------------------------------------------------------------------------------
1 | package definition
2 |
3 | import "github.com/sunist-c/genius-invokation-simulator-backend/enum"
4 |
5 | type Damage interface {
6 | ElementType() enum.ElementType
7 | DamageAmount() uint
8 | }
9 |
--------------------------------------------------------------------------------
/enum/modifier.go:
--------------------------------------------------------------------------------
1 | package enum
2 |
3 | type ModifierType byte
4 |
5 | const (
6 | ModifierTypeNone ModifierType = iota
7 | ModifierTypeAttack
8 | ModifierTypeCharacter
9 | ModifierTypeCharge
10 | ModifierTypeCost
11 | ModifierTypeDefence
12 | ModifierTypeHeal
13 | )
14 |
--------------------------------------------------------------------------------
/mod/definition/context.go:
--------------------------------------------------------------------------------
1 | package definition
2 |
3 | import "github.com/sunist-c/genius-invokation-simulator-backend/entity/model"
4 |
5 | type Context interface {
6 | ActiveCharacter() model.Character
7 | BackgroundCharacters() []model.Character
8 | Self() model.Player
9 | Players() []model.Player
10 | }
11 |
--------------------------------------------------------------------------------
/protocol/http/service/init.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "github.com/sunist-c/genius-invokation-simulator-backend/protocol/http/config"
5 | )
6 |
7 | func InitServices(conf config.EngineConfig) {
8 | SetConfig(conf)
9 | initPlayerService()
10 | initLocalizeService()
11 | initCardDeckService()
12 | initRoomInfoService()
13 | }
14 |
--------------------------------------------------------------------------------
/model/modifier/modifier.go:
--------------------------------------------------------------------------------
1 | package modifier
2 |
3 | import (
4 | "github.com/sunist-c/genius-invokation-simulator-backend/enum"
5 | )
6 |
7 | type Modifier[data any] interface {
8 | ID() uint64
9 | Type() enum.ModifierType
10 | Handler() func(ctx *Context[data])
11 | Clone() Modifier[data]
12 | RoundReset()
13 | Effective() bool
14 | EffectLeft() uint
15 | }
16 |
--------------------------------------------------------------------------------
/enum/equipment.go:
--------------------------------------------------------------------------------
1 | package enum
2 |
3 | // EquipmentType 装备类型
4 | type EquipmentType byte
5 |
6 | const (
7 | EquipmentNone EquipmentType = iota // EquipmentNone 无装备
8 | EquipmentWeapon // EquipmentWeapon 武器
9 | EquipmentArtifact // EquipmentArtifact 圣遗物
10 | EquipmentTalent // EquipmentTalent 天赋
11 | )
12 |
--------------------------------------------------------------------------------
/model/kv/map.go:
--------------------------------------------------------------------------------
1 | package kv
2 |
3 | // Pair key-value存储对的封装
4 | type Pair[key, value any] interface {
5 | Key() key
6 | Value() value
7 | SetKey(key)
8 | SetValue(value)
9 | }
10 |
11 | // Map key-value存储的封装
12 | type Map[key comparable, value any] interface {
13 | Exists(key) bool
14 | Get(key) value
15 | Set(key, value)
16 | Remove(key)
17 | Range(func(key, value) bool)
18 | }
19 |
--------------------------------------------------------------------------------
/protocol/http/service/config.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "github.com/sunist-c/genius-invokation-simulator-backend/protocol/http/config"
5 | )
6 |
7 | var (
8 | middlewareConfig config.MiddlewareConfig
9 | serviceConfig config.ServiceConfig
10 | )
11 |
12 | func SetConfig(conf config.EngineConfig) {
13 | middlewareConfig = conf.Middleware
14 | serviceConfig = conf.Service
15 | }
16 |
--------------------------------------------------------------------------------
/util/logger.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | type logger struct{}
4 |
5 | func Trace(chan interface{}) {}
6 |
7 | func Info(chan interface{}) {}
8 |
9 | func Error(chan error) {}
10 |
11 | type TraceLogger struct{}
12 |
13 | type InfoLogger struct{}
14 |
15 | type ErrorHandler struct {
16 | errorChan chan error
17 | }
18 |
19 | func (e *ErrorHandler) Handle(err error) {
20 | e.errorChan <- err
21 | }
22 |
--------------------------------------------------------------------------------
/entity/model/event.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "github.com/sunist-c/genius-invokation-simulator-backend/enum"
5 | "github.com/sunist-c/genius-invokation-simulator-backend/model/context"
6 | )
7 |
8 | type Event interface {
9 | BaseEntity
10 | TriggerAt() enum.TriggerType
11 | CanTriggered(context.CallbackContext) bool
12 | NeedClear() bool
13 | Callback(*context.CallbackContext)
14 | }
15 |
--------------------------------------------------------------------------------
/mod/definition/rule.go:
--------------------------------------------------------------------------------
1 | package definition
2 |
3 | import (
4 | "github.com/sunist-c/genius-invokation-simulator-backend/entity/model"
5 | "github.com/sunist-c/genius-invokation-simulator-backend/enum"
6 | )
7 |
8 | type Rule interface {
9 | model.BaseEntity
10 | CopyFrom(source Rule, filter ...enum.RuleType)
11 | Implements(ruleType enum.RuleType) interface{}
12 | CheckImplements() (success bool)
13 | }
14 |
--------------------------------------------------------------------------------
/enum/skill.go:
--------------------------------------------------------------------------------
1 | package enum
2 |
3 | // SkillType 技能类型
4 | type SkillType byte
5 |
6 | const (
7 | SkillPassive SkillType = iota // SkillPassive 被动技能
8 | SkillNormalAttack // SkillNormalAttack 普通攻击
9 | SkillElementalSkill // SkillElementalSkill 元素战技
10 | SkillElementalBurst // SkillElementalBurst 元素爆发
11 | SkillCooperative // SkillCooperative 协同攻击技能
12 | )
13 |
--------------------------------------------------------------------------------
/mod/definition/character.go:
--------------------------------------------------------------------------------
1 | package definition
2 |
3 | import (
4 | "github.com/sunist-c/genius-invokation-simulator-backend/entity/model"
5 | "github.com/sunist-c/genius-invokation-simulator-backend/enum"
6 | )
7 |
8 | type Character interface {
9 | model.BaseEntity
10 | Name() string
11 | Affiliation() enum.Affiliation
12 | Vision() enum.ElementType
13 | Weapon() enum.WeaponType
14 | Skills() []Skill
15 | HP() uint
16 | MP() uint
17 | }
18 |
--------------------------------------------------------------------------------
/enum/weapon.go:
--------------------------------------------------------------------------------
1 | package enum
2 |
3 | // WeaponType 武器类型
4 | type WeaponType byte
5 |
6 | const (
7 | WeaponSword WeaponType = iota // WeaponSword 单手剑
8 | WeaponClaymore // WeaponClaymore 双手剑
9 | WeaponBow // WeaponBow 弓
10 | WeaponCatalyst // WeaponCatalyst 法器
11 | WeaponPolearm // WeaponPolearm 长柄武器
12 | WeaponOthers // WeaponOthers 其他武器
13 | )
14 |
--------------------------------------------------------------------------------
/enum/stage.go:
--------------------------------------------------------------------------------
1 | package enum
2 |
3 | // RoundStage 回合生命周期
4 | type RoundStage byte
5 |
6 | const (
7 | RoundStageInitialized RoundStage = iota // RoundStageInitialized 初始化完成阶段,仅在游戏开始时进入
8 | RoundStageStart // RoundStageStart 回合开始阶段
9 | RoundStageRoll // RoundStageRoll 投掷阶段
10 | RoundStageBattle // RoundStageBattle 对战阶段
11 | RoundStageEnd // RoundStageEnd 回合结束阶段
12 | )
13 |
--------------------------------------------------------------------------------
/mod/definition/mod.go:
--------------------------------------------------------------------------------
1 | package definition
2 |
3 | type Mod interface {
4 | CharacterFactory
5 | SkillFactory
6 | EventFactory
7 | SummonFactory
8 | CardFactory
9 | RuleFactory
10 | LanguagePackFactory
11 | RegisterCharacter(character Character)
12 | RegisterSkill(skill Skill)
13 | RegisterEvent(event Event)
14 | RegisterSummon(summon Summon)
15 | RegisterCard(card Card)
16 | RegisterRule(rule Rule)
17 | AttachLanguagePack(languagePack LanguagePack)
18 | }
19 |
--------------------------------------------------------------------------------
/protocol/http/message/localization.go:
--------------------------------------------------------------------------------
1 | package message
2 |
3 | import "github.com/sunist-c/genius-invokation-simulator-backend/model/localization"
4 |
5 | type LocalizationQueryResponse struct {
6 | LanguagePack localization.MultipleLanguagePack `json:"language_pack"`
7 | }
8 |
9 | type TranslationRequest struct {
10 | Words []string `json:"words"`
11 | }
12 |
13 | type TranslationResponse struct {
14 | Translation map[string]string `json:"translation"`
15 | }
16 |
--------------------------------------------------------------------------------
/mod/definition/event.go:
--------------------------------------------------------------------------------
1 | package definition
2 |
3 | import (
4 | "github.com/sunist-c/genius-invokation-simulator-backend/entity/model"
5 | "github.com/sunist-c/genius-invokation-simulator-backend/enum"
6 | "github.com/sunist-c/genius-invokation-simulator-backend/model/context"
7 | )
8 |
9 | type Event interface {
10 | model.BaseEntity
11 | TriggerAt() enum.TriggerType
12 | TriggeredNow(ctx context.CallbackContext) bool
13 | ClearNow() bool
14 | CallBack(callbackContext *context.CallbackContext)
15 | }
16 |
--------------------------------------------------------------------------------
/exec/cli/main.go:
--------------------------------------------------------------------------------
1 | package cli
2 |
3 | import (
4 | "bufio"
5 | "fmt"
6 | "os"
7 | "strings"
8 | "time"
9 | )
10 |
11 | func Run() {
12 | for {
13 | if input, _, err := bufio.NewReader(os.Stdin).ReadLine(); err != nil {
14 | fmt.Printf("[cli.log] error occurred in cli.Run() %v\n", err)
15 | time.Sleep(time.Second)
16 | } else {
17 | if strings.Split(string(input), " ")[0] == "exit" {
18 | break
19 | }
20 | fmt.Printf("%+v\n", string(input))
21 | }
22 | }
23 | }
24 |
25 | func Quit() {}
26 |
--------------------------------------------------------------------------------
/exec/backend/main.go:
--------------------------------------------------------------------------------
1 | package backend
2 |
3 | import (
4 | "github.com/sunist-c/genius-invokation-simulator-backend/protocol/http"
5 | "github.com/sunist-c/genius-invokation-simulator-backend/protocol/http/config"
6 | "github.com/sunist-c/genius-invokation-simulator-backend/protocol/http/service"
7 | )
8 |
9 | func Run(port uint) {
10 | errChan := make(chan error)
11 | service.InitServices(config.GetConfig())
12 | http.Serve(port, errChan)
13 |
14 | err := <-errChan
15 | panic(err)
16 | }
17 |
18 | func Quit() {}
19 |
--------------------------------------------------------------------------------
/model/kv/pair.go:
--------------------------------------------------------------------------------
1 | package kv
2 |
3 | type pair[key, value any] struct {
4 | k key
5 | v value
6 | }
7 |
8 | func (p pair[key, value]) Key() key {
9 | return p.k
10 | }
11 |
12 | func (p pair[key, value]) Value() value {
13 | return p.v
14 | }
15 |
16 | func (p *pair[key, value]) SetKey(k key) {
17 | p.k = k
18 | }
19 |
20 | func (p *pair[key, value]) SetValue(v value) {
21 | p.v = v
22 | }
23 |
24 | func NewPair[key, value any](k key, v value) Pair[key, value] {
25 | return &pair[key, value]{
26 | k: k,
27 | v: v,
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/protocol/websocket/message/initialize.go:
--------------------------------------------------------------------------------
1 | package message
2 |
3 | import "github.com/sunist-c/genius-invokation-simulator-backend/enum"
4 |
5 | type MatchingMessage struct {
6 | UID uint64
7 | Characters []uint64
8 | CardDeck []uint64
9 | }
10 |
11 | type GameOptions struct {
12 | ReRollTime uint
13 | ElementAmount uint
14 | GetCards uint
15 | StaticElement map[enum.ElementType]uint
16 | RuleSet uint64
17 | }
18 |
19 | type InitializeMessage struct {
20 | Players []MatchingMessage
21 | Options GameOptions
22 | }
23 |
--------------------------------------------------------------------------------
/enum/character.go:
--------------------------------------------------------------------------------
1 | package enum
2 |
3 | // CharacterStatus 角色的状态
4 | type CharacterStatus byte
5 |
6 | const (
7 | CharacterStatusReady CharacterStatus = iota // CharacterStatusReady 角色已就绪,无状态
8 | CharacterStatusActive // CharacterStatusActive 角色已激活,前台角色
9 | CharacterStatusBackground // CharacterStatusBackground 角色已激活,后台角色
10 | CharacterStatusDisabled // CharacterStatusDisabled 前台角色,但无法进行操作
11 | CharacterStatusDefeated // CharacterStatusDefeated 角色已被击败,不可操作
12 | )
13 |
--------------------------------------------------------------------------------
/mod/implement/benchmark_test.go:
--------------------------------------------------------------------------------
1 | package implement
2 |
3 | import (
4 | "github.com/sunist-c/genius-invokation-simulator-backend/enum"
5 | "testing"
6 | )
7 |
8 | func BenchmarkTestNewCharacterWithOpts(b *testing.B) {
9 | SetDebugFlag(true)
10 |
11 | for i := 0; i < b.N; i++ {
12 | NewCharacterWithOpts(
13 | WithCharacterID(1),
14 | WithCharacterName("Ganyu"),
15 | WithCharacterAffiliation(enum.AffiliationLiyue),
16 | WithCharacterHP(10),
17 | WithCharacterMP(2),
18 | WithCharacterVision(enum.ElementCryo),
19 | WithCharacterWeapon(enum.WeaponBow),
20 | )
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/mod/definition/factory.go:
--------------------------------------------------------------------------------
1 | package definition
2 |
3 | type CharacterFactory interface {
4 | ProduceCharacters() []Character
5 | }
6 |
7 | type SummonFactory interface {
8 | ProduceSummons() []Summon
9 | }
10 |
11 | type EventFactory interface {
12 | ProduceEvents() []Event
13 | }
14 |
15 | type SkillFactory interface {
16 | ProduceSkills() []Skill
17 | }
18 |
19 | type CardFactory interface {
20 | ProduceCards() []Card
21 | }
22 |
23 | type RuleFactory interface {
24 | ProduceRules() []Rule
25 | }
26 |
27 | type LanguagePackFactory interface {
28 | ProduceLanguagePacks() []LanguagePack
29 | }
30 |
--------------------------------------------------------------------------------
/enum/language.go:
--------------------------------------------------------------------------------
1 | package enum
2 |
3 | type Language uint
4 |
5 | const (
6 | ChineseSimplified Language = iota // ChineseSimplified 简体中文
7 | ChineseTraditional // ChineseTraditional 繁體中文
8 | English // English
9 | French // French Français
10 | German // German Deutsch
11 | Japanese // Japanese 日本語
12 | Korean // Korean 한국어
13 | Russian // Russian Русский язык
14 | Unknown // Unknown ያንሽቤ
15 | )
16 |
--------------------------------------------------------------------------------
/.github/workflows/go.yml:
--------------------------------------------------------------------------------
1 | name: gisb-test
2 | on: [push, pull_request]
3 | jobs:
4 | build:
5 | name: build
6 | runs-on: ubuntu-latest
7 | steps:
8 | - name: Initialize
9 | uses: actions/setup-go@v1
10 | with:
11 | go-version: 1.19
12 | id: go
13 |
14 | - name: Checkout
15 | uses: actions/checkout@v1
16 |
17 | - name: Build
18 | run: go mod tidy && go build .
19 |
20 | - name: UnitTest
21 | run: go mod tidy && go test -v ./...
22 |
23 | - name: BenchmarkTest
24 | run: go mod tidy && go test -bench=. -benchtime=1s ./... -run=^$
--------------------------------------------------------------------------------
/entity/model/skill.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "github.com/sunist-c/genius-invokation-simulator-backend/enum"
5 | "github.com/sunist-c/genius-invokation-simulator-backend/model/context"
6 | )
7 |
8 | type Skill interface {
9 | BaseEntity
10 | Type() enum.SkillType
11 | }
12 |
13 | type AttackSkill interface {
14 | Skill
15 | Cost() Cost
16 | BaseDamage(targetCharacter, senderCharacter uint64, backgroundCharacters []uint64) *context.DamageContext
17 | }
18 |
19 | type CooperativeSkill interface {
20 | Skill
21 | BaseDamage() *context.DamageContext
22 | EffectLeft() uint
23 | Effective() bool
24 | }
25 |
26 | type PassiveSkill interface {
27 | }
28 |
--------------------------------------------------------------------------------
/mod/definition/skill.go:
--------------------------------------------------------------------------------
1 | package definition
2 |
3 | import (
4 | "github.com/sunist-c/genius-invokation-simulator-backend/entity/model"
5 | "github.com/sunist-c/genius-invokation-simulator-backend/enum"
6 | )
7 |
8 | type Skill interface {
9 | model.BaseEntity
10 | SkillType() enum.SkillType
11 | }
12 |
13 | type AttackSkill interface {
14 | Skill
15 | SkillCost() map[enum.ElementType]uint
16 | ActiveDamage(ctx Context) Damage
17 | BackgroundDamage(ctx Context) Damage
18 | }
19 |
20 | type CooperativeSkill interface {
21 | Skill
22 | ActiveDamage(ctx Context) Damage
23 | BackgroundDamage(ctx Context) Damage
24 | EffectLeft(ctx Context) uint
25 | Effective(ctx Context) bool
26 | }
27 |
--------------------------------------------------------------------------------
/mod/definition/card.go:
--------------------------------------------------------------------------------
1 | package definition
2 |
3 | import (
4 | "github.com/sunist-c/genius-invokation-simulator-backend/entity/model"
5 | "github.com/sunist-c/genius-invokation-simulator-backend/enum"
6 | )
7 |
8 | type Card interface {
9 | model.BaseEntity
10 | CardType() enum.CardType
11 | Cost() map[enum.ElementType]uint
12 | }
13 |
14 | type EventCard interface {
15 | Card
16 | Event() Event
17 | }
18 |
19 | type SupportCard interface {
20 | Card
21 | Support() Event
22 | }
23 |
24 | type EquipmentCard interface {
25 | Card
26 | EquipmentType() enum.EquipmentType
27 | Modify() Event
28 | }
29 |
30 | type WeaponCard interface {
31 | EquipmentCard
32 | WeaponType() enum.WeaponType
33 | }
34 |
--------------------------------------------------------------------------------
/util/password.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import "golang.org/x/crypto/scrypt"
4 |
5 | // EncodeRandomSalt 对任意类型的key进行编码生成一个长度为8的[]byte结果
6 | func EncodeRandomSalt[PK any](key PK) []byte {
7 | hashed := GenerateHash(key)
8 | salt := make([]byte, 8)
9 | for i := 0; i < 8; i++ {
10 | salt[i] = byte(hashed % 256)
11 | hashed = hashed >> 8
12 | }
13 |
14 | return salt
15 | }
16 |
17 | // EncodePassword 使用crypto/scrypt密钥库对密码进行单向加密
18 | func EncodePassword[PK any](original []byte, key PK) (success bool, result []byte) {
19 | if encrypted, err := scrypt.Key(original, EncodeRandomSalt(key), 32768, 8, 1, 64); err != nil {
20 | return false, []byte{}
21 | } else {
22 | return true, encrypted
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/enum/affiliation.go:
--------------------------------------------------------------------------------
1 | package enum
2 |
3 | // Affiliation 角色的归属势力
4 | type Affiliation byte
5 |
6 | const (
7 | AffiliationMondstadt Affiliation = iota // AffiliationMondstadt 蒙德人
8 | AffiliationLiyue // AffiliationLiyue 璃月人
9 | AffiliationInazuma // AffiliationInazuma 稻妻人
10 | AffiliationSumeru // AffiliationSumeru 须弥人
11 | AffiliationFatui Affiliation = 1<<4 + iota // AffiliationFatui 愚人众
12 | AffiliationHilichurl // AffiliationHilichurl 丘丘人
13 | AffiliationMonster // AffiliationMonster 魔物
14 | AffiliationUndefined Affiliation = 1<<8 - 1 // AffiliationUndefined 未定义
15 | )
16 |
--------------------------------------------------------------------------------
/enum/player.go:
--------------------------------------------------------------------------------
1 | package enum
2 |
3 | // PlayerStatus 玩家的状态
4 | type PlayerStatus byte
5 |
6 | const (
7 | PlayerStatusInitialized PlayerStatus = iota // PlayerStatusInitialized 玩家初始化完成
8 | PlayerStatusReady // PlayerStatusReady 玩家在回合开始阶段准备
9 | PlayerStatusWaiting // PlayerStatusWaiting 玩家在等待其他玩家操作
10 | PlayerStatusActing // PlayerStatusActing 玩家正在执行操作
11 | PlayerStatusDefeated // PlayerStatusDefeated 玩家被击败
12 | PlayerStatusViewing // PlayerStatusViewing 玩家正在观战
13 | PlayerStatusCharacterDefeated // PlayerStatusCharacterDefeated 玩家前台角色被击败,正在切换角色
14 | )
15 |
--------------------------------------------------------------------------------
/util/benchmark_test.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "math/rand"
5 | "sync/atomic"
6 | "testing"
7 | "time"
8 | )
9 |
10 | func BenchmarkTestGenerateID(b *testing.B) {
11 | mac := rand.Uint64() % (1 << 48)
12 | b.ResetTimer()
13 | for i := 0; i < b.N; i++ {
14 | GenerateUID(mac, time.Now())
15 | }
16 | }
17 |
18 | func BenchmarkGenerateRealID(b *testing.B) {
19 | mac := rand.Uint64() % (1 << 48)
20 | uids := make([]uint64, 114514)
21 | for i := 0; i < 114514; i++ {
22 | uids[i] = GenerateUID(mac, time.Now())
23 | }
24 |
25 | var index int32 = 0
26 | b.ResetTimer()
27 | for i := 0; i < b.N; i++ {
28 | if index >= 114514 {
29 | atomic.StoreInt32(&index, 0)
30 | }
31 | GenerateRealID(uids[index], uint16(index))
32 | atomic.AddInt32(&index, 1)
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/enum/action.go:
--------------------------------------------------------------------------------
1 | package enum
2 |
3 | // ActionType 玩家可进行的操作类型
4 | type ActionType byte
5 |
6 | const (
7 | ActionNone ActionType = iota // ActionNone 没有操作
8 | ActionNormalAttack // ActionNormalAttack 进行普通攻击
9 | ActionElementalSkill // ActionElementalSkill 释放元素战技
10 | ActionElementalBurst // ActionElementalBurst 释放元素爆发
11 | ActionSwitch // ActionSwitch 切换在场角色
12 | ActionBurnCard // ActionBurnCard 将卡牌转换为元素
13 | ActionUseCard // ActionUseCard 使用卡牌
14 | ActionReRoll // ActionReRoll 重掷元素骰子
15 | ActionSkipRound // ActionSkipRound 结束回合
16 | ActionConcede // ActionConcede 弃权让步
17 | )
18 |
--------------------------------------------------------------------------------
/enum/trigger.go:
--------------------------------------------------------------------------------
1 | package enum
2 |
3 | // TriggerType 触发器类型
4 | type TriggerType byte
5 |
6 | const (
7 | AfterNone TriggerType = iota // AfterNone 不会被触发的触发器
8 | AfterAttack // AfterAttack 攻击结算后触发
9 | AfterBurnCard // AfterBurnCard 使用卡牌转换元素后触发
10 | AfterCharge // AfterCharge 充能后触发
11 | AfterDefence // AfterDefence 防御结算后触发
12 | AfterEatFood // AfterEatFood 使用食物后触发
13 | AfterHeal // AfterHeal 治疗后触发
14 | AfterReset // AfterReset 重置时触发
15 | AfterRoundStart // AfterRoundStart 回合开始后触发
16 | AfterRoundEnd // AfterRoundEnd 回合结束后触发
17 | AfterSwitch // AfterSwitch 切换角色后触发
18 | )
19 |
--------------------------------------------------------------------------------
/model/context/charge.go:
--------------------------------------------------------------------------------
1 | package context
2 |
3 | type ChargeContext struct {
4 | charges map[uint64]int
5 | }
6 |
7 | // AddMagic 向指定目标添加能量
8 | func (c *ChargeContext) AddMagic(target uint64, amount uint) {
9 | c.charges[target] += int(amount)
10 | }
11 |
12 | // SubMagic 减少指定目标的能量
13 | func (c *ChargeContext) SubMagic(target uint64, amount uint) {
14 | c.charges[target] -= int(amount)
15 | }
16 |
17 | // Charge 返回ChargeContext携带的充能信息,只读
18 | func (c ChargeContext) Charge() map[uint64]int {
19 | result := map[uint64]int{}
20 | for target, amount := range c.charges {
21 | result[target] = amount
22 | }
23 |
24 | return result
25 | }
26 |
27 | // NewChargeContext 新建一个空的ChargeContext
28 | func NewChargeContext() *ChargeContext {
29 | return &ChargeContext{
30 | charges: map[uint64]int{},
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/protocol/http/middleware/uuid.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "github.com/sunist-c/genius-invokation-simulator-backend/protocol/http/config"
6 | "github.com/sunist-c/genius-invokation-simulator-backend/util"
7 | )
8 |
9 | // GetUUID 获取context中携带的uuid信息,若没有则生成并写入一个uuid
10 | func GetUUID(ctx *gin.Context, conf config.MiddlewareConfig) (uuid string) {
11 | if result, gotten := ctx.Get(conf.UUIDKey); !gotten {
12 | uuid = util.GenerateUUID()
13 | ctx.Set(conf.UUIDKey, uuid)
14 | return uuid
15 | } else {
16 | return result.(string)
17 | }
18 | }
19 |
20 | // NewUUIDTagger 创建一个UUID标记器,将会往context中写入uuid
21 | func NewUUIDTagger(conf config.MiddlewareConfig) func(ctx *gin.Context) {
22 | return func(ctx *gin.Context) {
23 | ctx.Set(conf.UUIDKey, util.GenerateUUID())
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/model/context/heal.go:
--------------------------------------------------------------------------------
1 | package context
2 |
3 | type HealContext struct {
4 | heals map[uint64]uint
5 | }
6 |
7 | // AddHeal 向指定目标添加治疗量
8 | func (h *HealContext) AddHeal(target uint64, amount uint) {
9 | h.heals[target] += amount
10 | }
11 |
12 | // SubHeal 减少指定目标的治疗量
13 | func (h *HealContext) SubHeal(target uint64, amount uint) {
14 | if h.heals[target] > amount {
15 | h.heals[target] -= amount
16 | } else {
17 | h.heals[target] = 0
18 | }
19 | }
20 |
21 | // Heal 返回HealContext携带的治疗信息,只读
22 | func (h HealContext) Heal() map[uint64]uint {
23 | result := map[uint64]uint{}
24 | for target, amount := range h.heals {
25 | result[target] = amount
26 | }
27 |
28 | return result
29 | }
30 |
31 | // NewHealContext 新建一个空的HealContext
32 | func NewHealContext() *HealContext {
33 | return &HealContext{
34 | heals: map[uint64]uint{},
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/mod/implement/entity.go:
--------------------------------------------------------------------------------
1 | package implement
2 |
3 | import (
4 | "github.com/sunist-c/genius-invokation-simulator-backend/entity/model"
5 | "github.com/sunist-c/genius-invokation-simulator-backend/util"
6 | )
7 |
8 | type EntityImpl struct {
9 | typeID uint64
10 | }
11 |
12 | func (b *EntityImpl) TypeID() uint64 {
13 | return b.typeID
14 | }
15 |
16 | func (b *EntityImpl) InjectTypeID(id uint64) {
17 | b.typeID = util.GenerateRealID(ModID(), uint16(id))
18 | }
19 |
20 | type EntityOptions func(entity *EntityImpl)
21 |
22 | func WithEntityID(id uint16) EntityOptions {
23 | return func(entity *EntityImpl) {
24 | entity.InjectTypeID(uint64(id))
25 | }
26 | }
27 |
28 | func NewEntityWithOpts(options ...EntityOptions) model.BaseEntity {
29 | entity := &EntityImpl{}
30 | for _, o := range options {
31 | o(entity)
32 | }
33 |
34 | return entity
35 | }
36 |
--------------------------------------------------------------------------------
/model/modifier/definition/modifier.go:
--------------------------------------------------------------------------------
1 | package definition
2 |
3 | import (
4 | "github.com/sunist-c/genius-invokation-simulator-backend/model/context"
5 | "github.com/sunist-c/genius-invokation-simulator-backend/model/modifier"
6 | )
7 |
8 | type AttackModifiers = modifier.Chain[context.DamageContext]
9 |
10 | type DefenceModifiers = modifier.Chain[context.DamageContext]
11 |
12 | type HealModifiers = modifier.Chain[context.HealContext]
13 |
14 | type ChargeModifiers = modifier.Chain[context.ChargeContext]
15 |
16 | type CostModifiers = modifier.Chain[context.CostContext]
17 |
18 | type AttackModifier = modifier.Modifier[context.DamageContext]
19 |
20 | type DefenceModifier = modifier.Modifier[context.DamageContext]
21 |
22 | type HealModifier = modifier.Modifier[context.HealContext]
23 |
24 | type ChargeModifier = modifier.Modifier[context.ChargeContext]
25 |
26 | type CostModifier = modifier.Modifier[context.CostContext]
27 |
--------------------------------------------------------------------------------
/model/context/cost.go:
--------------------------------------------------------------------------------
1 | package context
2 |
3 | import "github.com/sunist-c/genius-invokation-simulator-backend/enum"
4 |
5 | type CostContext struct {
6 | need map[enum.ElementType]int
7 | }
8 |
9 | // AddCost 增加指定ElementType的花费
10 | func (c *CostContext) AddCost(element enum.ElementType, amount uint) {
11 | c.need[element] += int(amount)
12 | }
13 |
14 | // SubCost 降低指定ElementType的花费
15 | func (c *CostContext) SubCost(element enum.ElementType, amount uint) {
16 | c.need[element] -= int(amount)
17 | }
18 |
19 | // Cost 返回CostContext携带的费用信息,只读
20 | func (c *CostContext) Cost() map[enum.ElementType]int {
21 | result := map[enum.ElementType]int{}
22 | for elementType, cost := range c.need {
23 | result[elementType] = cost
24 | }
25 |
26 | return result
27 | }
28 |
29 | // NewCostContext 新建一个空CostContext
30 | func NewCostContext() *CostContext {
31 | return &CostContext{
32 | need: map[enum.ElementType]int{},
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/model/kv/simple.go:
--------------------------------------------------------------------------------
1 | package kv
2 |
3 | type simpleMap[key comparable, value any] struct {
4 | data map[key]value
5 | }
6 |
7 | func (m simpleMap[key, value]) Exists(k key) bool {
8 | _, ok := m.data[k]
9 | return ok
10 | }
11 |
12 | func (m simpleMap[key, value]) Get(k key) value {
13 | return m.data[k]
14 | }
15 |
16 | func (m *simpleMap[key, value]) Set(k key, data value) {
17 | m.data[k] = data
18 | }
19 |
20 | func (m *simpleMap[key, value]) Remove(k key) {
21 | delete(m.data, k)
22 | }
23 |
24 | func (m *simpleMap[key, value]) Range(f func(key, value) bool) {
25 | for k, v := range m.data {
26 | if !f(k, v) {
27 | return
28 | }
29 | }
30 | }
31 |
32 | func NewSimpleMap[value any]() Map[uint64, value] {
33 | return &simpleMap[uint64, value]{data: map[uint64]value{}}
34 | }
35 |
36 | func NewCommonMap[key comparable, value any]() Map[key, value] {
37 | return &simpleMap[key, value]{data: map[key]value{}}
38 | }
39 |
--------------------------------------------------------------------------------
/enum/element.go:
--------------------------------------------------------------------------------
1 | package enum
2 |
3 | // ElementType 元素类型,七元素与通用元素
4 | type ElementType byte
5 |
6 | const (
7 | ElementCurrency ElementType = iota // ElementCurrency 通用元素,用于表示任意元素
8 | ElementAnemo // ElementAnemo 风元素
9 | ElementCryo // ElementCryo 冰元素
10 | ElementDendro // ElementDendro 草元素
11 | ElementElectro // ElementElectro 雷元素
12 | ElementGeo // ElementGeo 岩元素
13 | ElementHydro // ElementHydro 水元素
14 | ElementPyro // ElementPyro 火元素
15 | ElementNone ElementType = 1 << 4 // ElementNone 无元素,用于表示物理攻击
16 | ElementSame ElementType = 1 << 4 // ElementNone 无元素,用于表示相同元素
17 | ElementUndefined ElementType = 1<<8 - 1 // ElementUndefined 未定义元素,转化错误时传出此元素
18 |
19 | ElementStartIndex ElementType = 1 // ElementStartIndex 七元素起始
20 | ElementEndIndex ElementType = 7 // ElementEndIndex 七元素终止
21 | )
22 |
--------------------------------------------------------------------------------
/protocol/http/config/engine.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | var (
4 | config = &EngineConfig{
5 | Middleware: MiddlewareConfig{
6 | UUIDKey: "uuid",
7 | IPTranceKey: "ip",
8 | InterdictorTraceKey: "interdicted",
9 | InterdictorBlockedTime: 600,
10 | InterdictorTriggerCount: 5,
11 | QPSLimitTime: 1,
12 | TokenIDKey: "gisb_token_id",
13 | TokenKey: "gisb_token",
14 | TokenRefreshTime: 7200,
15 | TokenRemainingTime: 1800,
16 | CookieDomain: "localhost",
17 | },
18 | Service: ServiceConfig{
19 | MaxRooms: 100,
20 | },
21 | }
22 | )
23 |
24 | type EngineConfig struct {
25 | Middleware MiddlewareConfig `json:"middleware" yaml:"middleware" xml:"middleware"`
26 | Service ServiceConfig `json:"service" yaml:"service" xml:"service"`
27 | }
28 |
29 | func SetConfig(conf EngineConfig) {
30 | config = &conf
31 | }
32 |
33 | func GetConfig() EngineConfig {
34 | return *config
35 | }
36 |
--------------------------------------------------------------------------------
/mod/definition/language.go:
--------------------------------------------------------------------------------
1 | package definition
2 |
3 | import "github.com/sunist-c/genius-invokation-simulator-backend/enum"
4 |
5 | type LanguagePack interface {
6 | Language() enum.Language
7 | ModDescription() ModDescription
8 | GetCharacterDescription(longID uint64) (has bool, description CharacterDescription)
9 | GetSkillDescription(longID uint64) (has bool, description SkillDescription)
10 | GetEventDescription(longID uint64) (has bool, description EventDescription)
11 | GetCardDescription(longID uint64) (has bool, description CardDescription)
12 | GetSummonDescription(longID uint64) (has bool, description SummonDescription)
13 | GetModifierDescription(longID uint64) (has bool, description ModifierDescription)
14 | AddCharacterDescription(description CharacterDescription)
15 | AddSkillDescription(description SkillDescription)
16 | AddEventDescription(description EventDescription)
17 | AddCardDescription(description CardDescription)
18 | AddModifierDescription(description ModifierDescription)
19 | }
20 |
--------------------------------------------------------------------------------
/protocol/http/engine.go:
--------------------------------------------------------------------------------
1 | package http
2 |
3 | import (
4 | "fmt"
5 | "github.com/gin-gonic/gin"
6 | "github.com/sunist-c/genius-invokation-simulator-backend/protocol/http/config"
7 | "github.com/sunist-c/genius-invokation-simulator-backend/protocol/http/middleware"
8 | )
9 |
10 | var nullHttpEngine *gin.Engine = nil
11 |
12 | var (
13 | httpEngine *gin.Engine
14 | )
15 |
16 | var (
17 | EngineMiddlewares = []gin.HandlerFunc{
18 | middleware.NewIPTracer(config.GetConfig().Middleware), // IP追踪器
19 | middleware.NewUUIDTagger(config.GetConfig().Middleware), // UUID标记器
20 | }
21 | )
22 |
23 | func init() {
24 | httpEngine = gin.Default()
25 | }
26 |
27 | func RegisterServices(subPath string) *gin.RouterGroup {
28 | if httpEngine == nullHttpEngine {
29 | panic("nil http engine")
30 | } else {
31 | return httpEngine.Group(subPath)
32 | }
33 | }
34 |
35 | func Serve(port uint, errChan chan error) {
36 | if err := httpEngine.Run(fmt.Sprintf("0.0.0.0:%v", port)); err != nil {
37 | errChan <- err
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/protocol/http/middleware/qps.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "github.com/sunist-c/genius-invokation-simulator-backend/model/kv"
6 | "github.com/sunist-c/genius-invokation-simulator-backend/protocol/http/config"
7 | "time"
8 | )
9 |
10 | // NewQPSLimiter 生成一个QPS限制器,以IP来源为基础
11 | func NewQPSLimiter(conf config.MiddlewareConfig) func(ctx *gin.Context) {
12 | var limiter = kv.NewSyncMap[time.Time]()
13 | return func(ctx *gin.Context) {
14 | if success, ip := GetIPTrace(ctx, conf); !success {
15 | // 无法成功获取客户端IP,返回BadRequest
16 | ctx.AbortWithStatus(400)
17 | } else {
18 | if limiter.Exists(ip) {
19 | if t := limiter.Get(ip); !t.Add(time.Duration(conf.QPSLimitTime) * time.Second).Before(time.Now()) {
20 | // 请求过快,返回PreconditionFailed
21 | ctx.AbortWithStatus(412)
22 | } else {
23 | // 正常请求,更新访问时间
24 | limiter.Set(ip, time.Now())
25 | }
26 | } else {
27 | // 之前未访问过,记录访问时间
28 | limiter.Set(ip, time.Now())
29 | }
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/docs/_sidebar.md:
--------------------------------------------------------------------------------
1 | * [首页](/)
2 | * [开发文档](/development/)
3 | * [API文档](/development/api/)
4 | * [房间](/development/api/room.md)
5 | * [玩家](/development/api/player.md)
6 | * [卡组](/development/api/card_deck.md)
7 | * [本地化](/development/api/localization.md)
8 | * [框架文档](/development/framework/)
9 | * [战斗框架](/development/framework/battle.md)
10 | * [通信框架](/development/framework/communication.md)
11 | * [网络框架](/development/framework/network.md)
12 | * [MOD文档](/development/mod/)
13 | * [MOD基础](/development/mod/guide.md)
14 | * [制作卡牌](/development/mod/card.md)
15 | * [制作角色](/development/mod/character.md)
16 | * [制作技能](/development/mod/skill.md)
17 | * [制作事件](/development/mod/event.md)
18 | * [制作召唤物](/development/mod/summon.md)
19 | * [制作支援物](/development/mod/support.md)
20 | * [使用其他语言制作MOD](/development/mod/scripts.md)
21 | * [发布MOD](/development/mod/publish.md)
22 | * [使用文档](/guide/)
23 | * [部署到服务器](/guide/deploy.md)
24 | * [加载MOD](/guide/modify.md)
25 | * [连接到服务器](/guide/connection.md)
26 |
--------------------------------------------------------------------------------
/mod/implement/damage.go:
--------------------------------------------------------------------------------
1 | package implement
2 |
3 | import (
4 | "github.com/sunist-c/genius-invokation-simulator-backend/enum"
5 | "github.com/sunist-c/genius-invokation-simulator-backend/mod/definition"
6 | )
7 |
8 | type DamageImpl struct {
9 | elementType enum.ElementType
10 | damageAmount uint
11 | }
12 |
13 | func (impl *DamageImpl) ElementType() enum.ElementType {
14 | return impl.elementType
15 | }
16 |
17 | func (impl *DamageImpl) DamageAmount() uint {
18 | return impl.damageAmount
19 | }
20 |
21 | type DamageOptions func(option *DamageImpl)
22 |
23 | func WithDamageElementType(elementType enum.ElementType) DamageOptions {
24 | return func(option *DamageImpl) {
25 | option.elementType = elementType
26 | }
27 | }
28 |
29 | func WithDamageAmount(amount uint) DamageOptions {
30 | return func(option *DamageImpl) {
31 | option.damageAmount = amount
32 | }
33 | }
34 |
35 | func NewDamageWithOpts(options ...DamageOptions) definition.Damage {
36 | impl := &DamageImpl{}
37 | for _, option := range options {
38 | option(impl)
39 | }
40 |
41 | return impl
42 | }
43 |
--------------------------------------------------------------------------------
/mod/adapter/rule.go:
--------------------------------------------------------------------------------
1 | package adapter
2 |
3 | import (
4 | "github.com/sunist-c/genius-invokation-simulator-backend/entity/model"
5 | "github.com/sunist-c/genius-invokation-simulator-backend/enum"
6 | "github.com/sunist-c/genius-invokation-simulator-backend/mod/definition"
7 | "github.com/sunist-c/genius-invokation-simulator-backend/mod/implement"
8 | "github.com/sunist-c/genius-invokation-simulator-backend/model/adapter"
9 | )
10 |
11 | type RuleSetAdapter struct{}
12 |
13 | func (r RuleSetAdapter) Convert(source definition.Rule) (success bool, result model.RuleSet) {
14 | adapterLayer := model.RuleSet{}
15 | adapterLayer.ID = source.TypeID()
16 | _, adapterLayer.ReactionCalculator = implement.RuleConvert[model.ReactionCalculator](source.Implements(enum.RuleTypeReactionCalculator))
17 | _, adapterLayer.VictorCalculator = implement.RuleConvert[model.VictorCalculator](source.Implements(enum.RuleTypeVictorCalculator))
18 |
19 | return true, adapterLayer
20 | }
21 |
22 | func NewRuleSetAdapter() adapter.Adapter[definition.Rule, model.RuleSet] {
23 | return RuleSetAdapter{}
24 | }
25 |
--------------------------------------------------------------------------------
/model/kv/sync.go:
--------------------------------------------------------------------------------
1 | package kv
2 |
3 | import "sync"
4 |
5 | type syncMap[value any] struct {
6 | mutex *sync.RWMutex
7 | data map[uint]value
8 | }
9 |
10 | func (s *syncMap[value]) Exists(key uint) bool {
11 | s.mutex.RLock()
12 | _, ok := s.data[key]
13 | s.mutex.RUnlock()
14 | return ok
15 | }
16 |
17 | func (s *syncMap[value]) Get(key uint) value {
18 | s.mutex.RLock()
19 | result := s.data[key]
20 | s.mutex.RUnlock()
21 | return result
22 | }
23 |
24 | func (s *syncMap[value]) Set(key uint, data value) {
25 | s.mutex.Lock()
26 | s.data[key] = data
27 | s.mutex.Unlock()
28 | }
29 |
30 | func (s *syncMap[value]) Remove(key uint) {
31 | s.mutex.Lock()
32 | delete(s.data, key)
33 | s.mutex.Unlock()
34 | }
35 |
36 | func (s *syncMap[value]) Range(f func(uint, value) bool) {
37 | s.mutex.RLock()
38 | for k, v := range s.data {
39 | if !f(k, v) {
40 | break
41 | }
42 | }
43 | s.mutex.RUnlock()
44 | }
45 |
46 | func NewSyncMap[value any]() Map[uint, value] {
47 | return &syncMap[value]{
48 | mutex: &sync.RWMutex{},
49 | data: map[uint]value{},
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/mod/implement/context.go:
--------------------------------------------------------------------------------
1 | package implement
2 |
3 | import (
4 | "github.com/sunist-c/genius-invokation-simulator-backend/entity/model"
5 | "github.com/sunist-c/genius-invokation-simulator-backend/mod/definition"
6 | )
7 |
8 | type ContextImpl struct {
9 | activeCharacter model.Character
10 | backgroundCharacters []model.Character
11 | self model.Player
12 | players []model.Player
13 | }
14 |
15 | func (impl *ContextImpl) ActiveCharacter() model.Character {
16 | return impl.activeCharacter
17 | }
18 |
19 | func (impl *ContextImpl) BackgroundCharacters() []model.Character {
20 | return impl.backgroundCharacters
21 | }
22 |
23 | func (impl *ContextImpl) Self() model.Player {
24 | return impl.self
25 | }
26 |
27 | func (impl *ContextImpl) Players() []model.Player {
28 | return impl.players
29 | }
30 |
31 | func NewEmptyContext() definition.Context {
32 | return &ContextImpl{
33 | activeCharacter: nil,
34 | backgroundCharacters: []model.Character{},
35 | self: nil,
36 | players: []model.Player{},
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/mod/definition/modifier.go:
--------------------------------------------------------------------------------
1 | package definition
2 |
3 | import (
4 | "github.com/sunist-c/genius-invokation-simulator-backend/enum"
5 | "github.com/sunist-c/genius-invokation-simulator-backend/model/context"
6 | "github.com/sunist-c/genius-invokation-simulator-backend/model/modifier"
7 | )
8 |
9 | type BaseModifier interface {
10 | ModifierID() uint64
11 | ModifierType() enum.ModifierType
12 | RoundStartReset()
13 | Effective() bool
14 | EffectLeft() uint
15 | }
16 |
17 | type AttackModifier interface {
18 | BaseModifier
19 | ModifyAttack() func(ctx *modifier.Context[context.DamageContext])
20 | Clone() AttackModifier
21 | }
22 |
23 | type CostModifier interface {
24 | BaseModifier
25 | ModifyCost() func(ctx *modifier.Context[context.CostContext])
26 | Clone() CostModifier
27 | }
28 |
29 | type HealModifier interface {
30 | BaseModifier
31 | ModifyHeal() func(ctx *modifier.Context[context.HealContext])
32 | Clone() HealModifier
33 | }
34 |
35 | type ChargeModifier interface {
36 | BaseModifier
37 | ModifyCharacter() func(ctx *modifier.Context[context.ChargeContext])
38 | Clone() ChargeModifier
39 | }
40 |
--------------------------------------------------------------------------------
/license:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Sunist
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/enum/card.go:
--------------------------------------------------------------------------------
1 | package enum
2 |
3 | // CardType 卡牌类型,大类
4 | type CardType byte
5 |
6 | // CardSubType 卡牌类型,小类
7 | type CardSubType = CardType
8 |
9 | const (
10 | CardEvent CardType = iota << 4 // CardEvent 事件卡
11 | CardSupport // CardSupport 支援卡
12 | CardEquipment // CardEquipment 装备卡
13 | CardUnknown CardType = 1 << 7
14 | )
15 |
16 | // EventCardSubType
17 | const (
18 | CardElementalResonance CardType = CardEvent + 1 + iota // CardElementalResonance 元素共鸣事件卡
19 | CardFood // CardFood 食物事件卡
20 | )
21 |
22 | // SupportCardSubType
23 | const (
24 | CardLocation CardType = CardSupport + 1 + iota // CardLocation 地点支援卡
25 | CardCompanion // CardCompanion 伙伴支援卡
26 | CardItem // CardItem 物品支援卡
27 | )
28 |
29 | // EquipmentCardSubType
30 | const (
31 | CardTalent CardType = CardEquipment + 1 + iota // CardTalent 天赋装备卡
32 | CardWeapon // CardWeapon 武器装备卡
33 | CardArtifact // CardArtifact 圣遗物装备卡
34 | )
35 |
--------------------------------------------------------------------------------
/mod/definition/description.go:
--------------------------------------------------------------------------------
1 | package definition
2 |
3 | import "github.com/sunist-c/genius-invokation-simulator-backend/enum"
4 |
5 | type Description interface {
6 | DescriptionType() enum.DescriptionType
7 | ShortID() uint16
8 | LongID() uint64
9 | }
10 |
11 | type CharacterDescription interface {
12 | Description
13 | CharacterName() string
14 | CharacterDescription() string
15 | CharacterTitle() string
16 | CharacterStory() string
17 | }
18 |
19 | type SkillDescription interface {
20 | Description
21 | SkillName() string
22 | SkillDescription() string
23 | }
24 |
25 | type EventDescription interface {
26 | Description
27 | EventName() string
28 | EventDescription() string
29 | }
30 |
31 | type CardDescription interface {
32 | Description
33 | CardName() string
34 | CardDescription() string
35 | }
36 |
37 | type SummonDescription interface {
38 | Description
39 | SummonName() string
40 | SummonDescription() string
41 | }
42 |
43 | type ModifierDescription interface {
44 | Description
45 | ModifierName() string
46 | ModifierDescription() string
47 | }
48 |
49 | type ModDescription interface {
50 | ModID() uint64
51 | ModName() string
52 | ModDescription() string
53 | }
54 |
--------------------------------------------------------------------------------
/.drone.yml:
--------------------------------------------------------------------------------
1 | kind: pipeline
2 | type: docker
3 | name: build
4 |
5 | clone:
6 | disable: false
7 | depth: 1
8 |
9 | steps:
10 | - name: golangci-lint
11 | image: golang:1.19
12 | pull: if-not-exist
13 | commands:
14 | - go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.50.1
15 | - go mod tidy
16 | - golangci-lint run -v -D unused --timeout=10m
17 |
18 | - name: go build
19 | image: golang:1.19
20 | pull: if-not-exist
21 | commands:
22 | - go mod tidy
23 | - go build .
24 |
25 | - name: go test
26 | image: golang:1.19
27 | pull: if-not-exist
28 | commands:
29 | - go mod tidy
30 | - go test -v ./...
31 |
32 | - name: go bench test
33 | image: golang:1.19
34 | pull: if-not-exist
35 | commands:
36 | - go mod tidy
37 | - go test -bench=. -benchtime=1s ./... -run=^$
38 |
39 | - name: feishu notification
40 | image: serialt/drone-feishu-message
41 | pull: if-not-exists
42 | settings:
43 | token:
44 | from_secret: notification_token
45 | sha_link: true
46 | when:
47 | status: [ failure, success ]
--------------------------------------------------------------------------------
/protocol/http/config/middleware.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | type MiddlewareConfig struct {
4 | UUIDKey string `json:"uuid_key" yaml:"uuid_key" xml:"uuid_key"`
5 | IPTranceKey string `json:"ip_trace_key" yaml:"ip_trance_key" xml:"ip_trance_key"`
6 | InterdictorTraceKey string `json:"interdictor_trace_key" yaml:"interdictor_trace_key" xml:"interdictor_trace_key"`
7 | InterdictorBlockedTime uint `json:"interdictor_blocked_time" yaml:"interdictor_blocked_time" xml:"interdictor_blocked_time"`
8 | InterdictorTriggerCount uint `json:"interdictor_trigger_count" yaml:"interdictor_trigger_count" xml:"interdictor_trigger_count"`
9 | QPSLimitTime uint `json:"qps_limit_time" yaml:"qps_limit_time" xml:"qps_limit_time"`
10 | TokenIDKey string `json:"token_id_key" yaml:"token_id_key" xml:"token_id_key"`
11 | TokenKey string `json:"token_key" yaml:"token_key" xml:"token_key"`
12 | TokenRefreshTime uint `json:"token_refresh_time" yaml:"token_refresh_time" xml:"token_refresh_time"`
13 | TokenRemainingTime uint `json:"token_remaining_time" yaml:"token_remaining_time" xml:"token_remaining_time"`
14 | CookieDomain string `json:"cookie_domain" yaml:"cookie_domain" xml:"cookie_domain"`
15 | }
16 |
--------------------------------------------------------------------------------
/docs/en/_sidebar.md:
--------------------------------------------------------------------------------
1 | * [Home](/en/)
2 | * [Development](/en/development/)
3 | * [API](/en/development/api/)
4 | * [Room](/en/development/api/room.md)
5 | * [Player](/en/development/api/player.md)
6 | * [CardDeck](/en/development/api/card_deck.md)
7 | * [Localization](/en/development/api/localization.md)
8 | * [Framework](/en/development/framework/)
9 | * [Battle](/en/development/framework/battle.md)
10 | * [Communication](/en/development/framework/communication.md)
11 | * [Network](/en/development/framework/network.md)
12 | * [Mod](/en/development/mod/)
13 | * [Mod Basic](/en/development/mod/guide.md)
14 | * [Create Cards](/en/development/mod/card.md)
15 | * [Create Characters](/en/development/mod/character.md)
16 | * [Create Skills](/en/development/mod/skill.md)
17 | * [Create Events](/en/development/mod/event.md)
18 | * [Create Summons](/en/development/mod/summon.md)
19 | * [Create Supports](/en/development/mod/support.md)
20 | * [Use Other Coding Language](/en/development/mod/scripts.md)
21 | * [Publish Mod](/en/development/mod/publish.md)
22 | * [Guide](/en/guide/)
23 | * [Deploy to Server](/en/guide/deploy.md)
24 | * [Load Mods](/en/guide/modify.md)
25 | * [Connect to Sever](/en/guide/connection.md)
--------------------------------------------------------------------------------
/protocol/websocket/config/handler.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | var (
4 | websocketConfig = &WebsocketConfig{
5 | HandshakeTimeout: 60,
6 | AllowCrossOrigin: true,
7 | WebsocketWriterBufferSize: 4096,
8 | WebsocketReaderBufferSize: 4096,
9 | ServerMessageBufferSize: 128,
10 | AllowOriginDomains: []string{""},
11 | }
12 | )
13 |
14 | type WebsocketConfig struct {
15 | HandshakeTimeout uint `json:"handshake_timeout" yaml:"handshake_timeout" xml:"handshake_timeout"`
16 | AllowCrossOrigin bool `json:"allow_cross_origin" yaml:"allow_cross_origin" xml:"allow_cross_origin"`
17 | WebsocketWriterBufferSize uint `json:"websocket_writer_buffer_size" yaml:"websocket_writer_buffer_size" xml:"websocket_writer_buffer_size"`
18 | WebsocketReaderBufferSize uint `json:"websocket_reader_buffer_size" yaml:"websocket_reader_buffer_size" xml:"websocket_reader_buffer_size"`
19 | ServerMessageBufferSize uint `json:"server_message_buffer_size" yaml:"server_message_buffer_size" xml:"server_message_buffer_size"`
20 | AllowOriginDomains []string `json:"allow_origin_domains" yaml:"allow_origin_domains" xml:"allow_origin_domains"`
21 | }
22 |
23 | func SetConfig(conf WebsocketConfig) {
24 | *websocketConfig = conf
25 | }
26 |
27 | func GetConfig() WebsocketConfig {
28 | return *websocketConfig
29 | }
30 |
--------------------------------------------------------------------------------
/enum/reaction.go:
--------------------------------------------------------------------------------
1 | package enum
2 |
3 | // Reaction 反应类型
4 | type Reaction byte
5 |
6 | const (
7 | ReactionNone Reaction = iota // ReactionNone 无反应
8 | ReactionMelt // ReactionMelt 融化
9 | ReactionVaporize // ReactionVaporize 蒸发
10 | ReactionOverloaded // ReactionOverloaded 超载
11 | ReactionSuperconduct // ReactionSuperconduct 超导
12 | ReactionFrozen // ReactionFrozen 冻结
13 | ReactionElectroCharged // ReactionElectroCharged 感电
14 | ReactionBurning // ReactionBurning 燃烧
15 | ReactionBloom // ReactionBloom 绽放
16 | ReactionQuicken // ReactionQuicken 激化
17 | ReactionCryoSwirl // ReactionCryoSwirl 冰扩散
18 | ReactionElectroSwirl // ReactionElectroSwirl 电扩散
19 | ReactionHydroSwirl // ReactionHydroSwirl 水扩散
20 | ReactionPyroSwirl // ReactionPyroSwirl 火扩散
21 | ReactionCryoCrystalize // ReactionCryoCrystalize 冰结晶
22 | ReactionElectroCrystalize // ReactionElectroCrystalize 雷结晶
23 | ReactionHydroCrystalize // ReactionHydroCrystalize 水结晶
24 | ReactionPyroCrystalize // ReactionPyroCrystalize 火结晶
25 | )
26 |
--------------------------------------------------------------------------------
/model/modifier/context.go:
--------------------------------------------------------------------------------
1 | package modifier
2 |
3 | const abortIndex byte = 1 << 7
4 |
5 | type Context[data any] struct {
6 | index byte
7 | chain Chain[data]
8 | data *data
9 | extendMap map[string]any
10 | }
11 |
12 | // Next 让Context继续执行事件链
13 | func (c *Context[data]) Next() {
14 | c.index += 1
15 | for c.index <= c.chain.size {
16 | c.chain.index(c.index - 1).modifier.Handler()(c)
17 | c.index += 1
18 | }
19 | }
20 |
21 | // Abort 中断Context事件链的调用,但是在终止之前的调用仍会生效
22 | func (c *Context[data]) Abort() {
23 | c.index = abortIndex
24 | }
25 |
26 | // IsAborted 判断Context是否被某一handler中断了
27 | func (c Context[data]) IsAborted() bool {
28 | return c.index >= abortIndex
29 | }
30 |
31 | // Data 获取Context中的数据,只读
32 | func (c Context[data]) Data() *data {
33 | return c.data
34 | }
35 |
36 | // Get 在Context的extendMap中获取数据,不建议使用这种方法传递信息
37 | func (c Context[data]) Get(key string) (value any, ok bool) {
38 | value, ok = c.extendMap[key]
39 | return value, ok
40 | }
41 |
42 | // Set 在Context的extendMap中写入数据,不建议使用这种方法传递信息
43 | func (c *Context[data]) Set(key string, value any) {
44 | c.extendMap[key] = value
45 | }
46 |
47 | // newContext 使用给定的data和handlers生成Context
48 | func newContext[data any](ctx *data, handlers Chain[data]) *Context[data] {
49 | return &Context[data]{
50 | index: 0,
51 | chain: handlers,
52 | data: ctx,
53 | extendMap: map[string]any{},
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/protocol/http/message/carddeck.go:
--------------------------------------------------------------------------------
1 | package message
2 |
3 | type UploadCardDeckRequest struct {
4 | Owner uint64 `json:"owner"`
5 | RequiredPackage []string `json:"required_package"`
6 | Cards []uint64 `json:"cards"`
7 | Characters []uint64 `json:"characters"`
8 | }
9 |
10 | type UploadCardDeckResponse struct {
11 | ID uint64 `json:"id"`
12 | Owner uint64 `json:"owner"`
13 | RequiredPackage []string `json:"required_package"`
14 | Cards []uint64 `json:"cards"`
15 | Characters []uint64 `json:"characters"`
16 | }
17 |
18 | type UpdateCardDeckRequest struct {
19 | Owner uint64 `json:"owner"`
20 | RequiredPackage []string `json:"required_package"`
21 | Cards []uint64 `json:"cards"`
22 | Characters []uint64 `json:"characters"`
23 | }
24 |
25 | type UpdateCardDeckResponse struct {
26 | ID uint64 `json:"id"`
27 | Owner uint64 `json:"owner"`
28 | RequiredPackage []string `json:"required_package"`
29 | Cards []uint64 `json:"cards"`
30 | Characters []uint64 `json:"characters"`
31 | }
32 |
33 | type QueryCardDeckResponse struct {
34 | ID uint64 `json:"id"`
35 | Owner uint64 `json:"owner"`
36 | RequiredPackage []string `json:"required_package"`
37 | Cards []uint64 `json:"cards"`
38 | Characters []uint64 `json:"characters"`
39 | }
40 |
--------------------------------------------------------------------------------
/protocol/http/message/player.go:
--------------------------------------------------------------------------------
1 | package message
2 |
3 | type CardDeck struct {
4 | ID uint64 `json:"id"`
5 | OwnerUID uint64 `json:"owner_uid"`
6 | RequiredPackages []string `json:"required_packages"`
7 | Cards []uint64 `json:"cards"`
8 | Characters []uint64 `json:"characters"`
9 | }
10 |
11 | type LoginResponse struct {
12 | PlayerUID uint64 `json:"player_uid"`
13 | Success bool `json:"success"`
14 | PlayerNickName string `json:"player_nick_name"`
15 | PlayerCardDecks []CardDeck `json:"player_card_decks"`
16 | }
17 |
18 | type LoginRequest struct {
19 | PlayerUID uint64 `json:"player_uid"`
20 | Password string `json:"password"`
21 | }
22 |
23 | type RegisterRequest struct {
24 | NickName string `json:"nick_name"`
25 | Password string `json:"password"`
26 | }
27 |
28 | type RegisterResponse struct {
29 | PlayerUID uint64 `json:"player_uid"`
30 | PlayerNickName string `json:"player_nick_name"`
31 | }
32 |
33 | type UpdatePasswordRequest struct {
34 | OriginalPassword string `json:"original_password"`
35 | NewPassword string `json:"new_password"`
36 | }
37 |
38 | type UpdateNickNameRequest struct {
39 | Password string `json:"password"`
40 | NewNickName string `json:"new_nick_name"`
41 | }
42 |
43 | type DestroyPlayerRequest struct {
44 | Password string `json:"password"`
45 | Confirm bool `json:"confirm"`
46 | }
47 |
48 | type DestroyPlayerResponse struct {
49 | Success bool `json:"success"`
50 | }
51 |
--------------------------------------------------------------------------------
/entity/model/interface.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import "github.com/sunist-c/genius-invokation-simulator-backend/enum"
4 |
5 | // Player 供外部包调用的玩家接口
6 | type Player interface {
7 | GetUID() (uid uint64)
8 | GetCost() (cost map[enum.ElementType]uint)
9 | GetCards() (cards []uint64)
10 | GetSummons() (summons []uint64)
11 | GetSupports() (supports []uint64)
12 | CardDeckRemain() (remain uint)
13 | GetActiveCharacter() (character uint64)
14 | GetBackgroundCharacters() (characters []uint64)
15 | GetCharacter(character uint64) (has bool, entity Character)
16 | GetStatus() (status enum.PlayerStatus)
17 | GetGlobalModifiers(modifierType enum.ModifierType) (modifiers []uint64)
18 | GetCooperativeSkills(trigger enum.TriggerType) (skills []uint64)
19 | GetEvents(trigger enum.TriggerType) (events []uint64)
20 | }
21 |
22 | // Character 供外部包调用的角色接口
23 | type Character interface {
24 | GetID() (id uint64)
25 | GetOwner() (owner uint64)
26 | GetAffiliation() (affiliation enum.Affiliation)
27 | GetVision() (element enum.ElementType)
28 | GetWeaponType() (weaponType enum.WeaponType)
29 | GetSkills() (skills []uint64)
30 | GetHP() (hp uint)
31 | GetMaxHP() (maxHP uint)
32 | GetMP() (mp uint)
33 | GetMaxMP() (maxMP uint)
34 | GetEquipment(equipmentType enum.EquipmentType) (equipped bool, equipment uint64)
35 | GetSatiety() (satiety bool)
36 | GetAttachedElements() (elements []enum.ElementType)
37 | GetStatus() (status enum.CharacterStatus)
38 | GetLocalModifiers(modifierType enum.ModifierType) (modifiers []uint64)
39 | }
40 |
--------------------------------------------------------------------------------
/persistence/memcache.go:
--------------------------------------------------------------------------------
1 | package persistence
2 |
3 | import (
4 | "sync"
5 | )
6 |
7 | // memoryCache 内存缓存的实现,简单的并发安全map封装,同kv.syncMap实现
8 | type memoryCache[PK comparable, T any] struct {
9 | mu sync.RWMutex
10 | cache map[PK]T
11 | }
12 |
13 | func (m *memoryCache[PK, T]) QueryByID(id PK) (has bool, result T) {
14 | m.mu.RLock()
15 | defer m.mu.RUnlock()
16 | if entity, exist := m.cache[id]; !exist {
17 | return false, result
18 | } else {
19 | return true, entity
20 | }
21 | }
22 |
23 | func (m *memoryCache[PK, T]) UpdateByID(id PK, newEntity T) (success bool) {
24 | m.mu.RLock()
25 | defer m.mu.RUnlock()
26 | if _, exist := m.cache[id]; !exist {
27 | return false
28 | } else {
29 | m.cache[id] = newEntity
30 | return true
31 | }
32 | }
33 |
34 | func (m *memoryCache[PK, T]) InsertOne(id PK, entity T) (success bool) {
35 | m.mu.RLock()
36 | _, exist := m.cache[id]
37 | m.mu.RUnlock()
38 |
39 | if !exist {
40 | m.mu.Lock()
41 | m.cache[id] = entity
42 | m.mu.Unlock()
43 | return true
44 | } else {
45 | return false
46 | }
47 | }
48 |
49 | func (m *memoryCache[PK, T]) DeleteOne(id PK) (success bool) {
50 | m.mu.RLock()
51 | _, exist := m.cache[id]
52 | m.mu.RUnlock()
53 |
54 | if !exist {
55 | return false
56 | } else {
57 | m.mu.Lock()
58 | delete(m.cache, id)
59 | m.mu.Unlock()
60 | return true
61 | }
62 | }
63 |
64 | func newMemoryCache[PK comparable, T any]() MemoryCache[PK, T] {
65 | return &memoryCache[PK, T]{
66 | mu: sync.RWMutex{},
67 | cache: map[PK]T{},
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/model/localization/language.go:
--------------------------------------------------------------------------------
1 | package localization
2 |
3 | import "github.com/sunist-c/genius-invokation-simulator-backend/enum"
4 |
5 | type MultipleLanguagePack struct {
6 | Languages map[enum.Language]map[string]string `json:"languages"`
7 | SupportedLanguages []enum.Language `json:"supported_languages"`
8 | }
9 |
10 | // LanguagePack 语言包接口
11 | type LanguagePack interface {
12 | Translate(original string, destLanguage enum.Language) (ok bool, result string)
13 | Pack() MultipleLanguagePack
14 | }
15 |
16 | // languagePack 语言包
17 | type languagePack struct {
18 | translations map[enum.Language]map[string]string
19 | languages []enum.Language
20 | }
21 |
22 | func (l languagePack) Translate(original string, destLanguage enum.Language) (ok bool, result string) {
23 | if pack, hasLanguage := l.translations[destLanguage]; !hasLanguage {
24 | return false, ""
25 | } else {
26 | result, ok = pack[original]
27 | return ok, result
28 | }
29 | }
30 |
31 | func (l languagePack) Pack() MultipleLanguagePack {
32 | pack := MultipleLanguagePack{
33 | Languages: l.translations,
34 | SupportedLanguages: l.languages,
35 | }
36 |
37 | return pack
38 | }
39 |
40 | func NewLanguagePack(translations map[enum.Language]map[string]string) LanguagePack {
41 | pack := languagePack{
42 | translations: translations,
43 | languages: []enum.Language{},
44 | }
45 |
46 | for language := range translations {
47 | pack.languages = append(pack.languages, language)
48 | }
49 |
50 | return pack
51 | }
52 |
--------------------------------------------------------------------------------
/protocol/http/middleware/trace.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "fmt"
5 | "github.com/gin-gonic/gin"
6 | "github.com/sunist-c/genius-invokation-simulator-backend/protocol/http/config"
7 | "github.com/sunist-c/genius-invokation-simulator-backend/protocol/http/util"
8 | "strconv"
9 | "strings"
10 | )
11 |
12 | func GetIPTrace(ctx *gin.Context, conf config.MiddlewareConfig) (has bool, ip uint) {
13 | if result, gotten := ctx.Get(conf.IPTranceKey); !gotten {
14 | return false, 0
15 | } else if ipResult, ok := result.(uint); !ok {
16 | return false, 0
17 | } else {
18 | return true, ipResult
19 | }
20 | }
21 |
22 | func ConvertIPToUint(ip string) (success bool, result uint) {
23 | splits := strings.Split(ip, ".")
24 | if len(splits) != 4 {
25 | return false, 0
26 | } else {
27 | for _, s := range splits {
28 | if intResult, err := strconv.Atoi(s); err != nil {
29 | return false, 0
30 | } else if intResult > 256 || intResult < 0 {
31 | return false, 0
32 | } else {
33 | result = result<<8 + uint(intResult)
34 | }
35 | }
36 | return true, result
37 | }
38 | }
39 |
40 | func ConvertUintToIP(ip uint) (result string) {
41 | bytes := make([]byte, 4)
42 | for i := 0; i < 4; i++ {
43 | bytes[i] = byte(ip % 256)
44 | ip = ip >> 8
45 | }
46 | return fmt.Sprintf("%d.%d.%d.%d", bytes[0], bytes[1], bytes[2], bytes[3])
47 | }
48 |
49 | func NewIPTracer(conf config.MiddlewareConfig) func(ctx *gin.Context) {
50 | return func(ctx *gin.Context) {
51 | if success, ip := ConvertIPToUint(util.GetClientIP(ctx)); !success {
52 | // 无法成功获取客户端IP,返回BadRequest
53 | ctx.AbortWithStatus(400)
54 | } else {
55 | ctx.Set(conf.IPTranceKey, ip)
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/util/os.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "net"
5 | "strconv"
6 | "strings"
7 | "time"
8 | )
9 |
10 | type SystemBits byte
11 |
12 | const (
13 | BitsUnknown SystemBits = 0
14 | Bits32 SystemBits = 32
15 | Bits64 SystemBits = 64
16 | )
17 |
18 | var (
19 | zeroTimeStamp = new(time.Time)
20 | )
21 |
22 | func init() {
23 | // 初始化“零点”,为本项目的创建时间
24 | utc8timeZone := time.FixedZone("UTC+8", 8*60*60)
25 | *zeroTimeStamp = time.Date(2022, 12, 9, 23, 35, 0, 0, utc8timeZone)
26 | }
27 |
28 | // GetZeroTimeStamp 获取系统的零时
29 | func GetZeroTimeStamp() time.Time {
30 | return *zeroTimeStamp
31 | }
32 |
33 | // GetSystemBits 获取操作系统的位数,结果为SystemBits
34 | func GetSystemBits() SystemBits {
35 | bit := 32 << ((^uint(0)) >> 63)
36 |
37 | switch bit {
38 | case 32:
39 | return Bits32
40 | case 64:
41 | return Bits64
42 | default:
43 | return BitsUnknown
44 | }
45 | }
46 |
47 | // GetMacAddresses 使用net包获取本机mac地址
48 | func GetMacAddresses() (macAddr []net.Interface, err error) {
49 | return net.Interfaces()
50 | }
51 |
52 | // GetUintMacAddress 从net.Interface中获取一个uint64类型的地址,长度为48位
53 | func GetUintMacAddress(mac net.Interface) (addr uint64, err error) {
54 | macAddrArr := strings.Split(mac.HardwareAddr.String(), ":")
55 | macAddr := strings.Join(macAddrArr, "")
56 |
57 | // mac地址为空,考虑到权限问题,设置mac地址为随机变量
58 | if macAddr == "" {
59 | macPartBits := uint64(1<<48) - 1
60 | addr = GenerateHashWithOpts[net.Interface, uint64](mac)
61 | return addr & macPartBits, nil
62 | }
63 |
64 | // mac地址非空,则正常计算mac的48位数值
65 | if addr, err = strconv.ParseUint(macAddr, 16, 64); err != nil {
66 | return 0, err
67 | } else {
68 | macPartBits := uint64(1<<48) - 1
69 | return addr & macPartBits, nil
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/mod/adapter/event.go:
--------------------------------------------------------------------------------
1 | package adapter
2 |
3 | import (
4 | "github.com/sunist-c/genius-invokation-simulator-backend/entity/model"
5 | "github.com/sunist-c/genius-invokation-simulator-backend/enum"
6 | definition "github.com/sunist-c/genius-invokation-simulator-backend/mod/definition"
7 | "github.com/sunist-c/genius-invokation-simulator-backend/mod/implement"
8 | "github.com/sunist-c/genius-invokation-simulator-backend/model/adapter"
9 | "github.com/sunist-c/genius-invokation-simulator-backend/model/context"
10 | )
11 |
12 | type eventAdapterLayer struct {
13 | implement.EntityImpl
14 | triggerAt enum.TriggerType
15 | canTriggered func(callbackContext context.CallbackContext) bool
16 | needClear func() bool
17 | callback func(context *context.CallbackContext)
18 | }
19 |
20 | func (e *eventAdapterLayer) TriggerAt() enum.TriggerType {
21 | return e.triggerAt
22 | }
23 |
24 | func (e *eventAdapterLayer) CanTriggered(context context.CallbackContext) bool {
25 | return e.canTriggered(context)
26 | }
27 |
28 | func (e *eventAdapterLayer) NeedClear() bool {
29 | return e.needClear()
30 | }
31 |
32 | func (e *eventAdapterLayer) Callback(context *context.CallbackContext) {
33 | e.callback(context)
34 | }
35 |
36 | type EventAdapter struct{}
37 |
38 | func (e EventAdapter) Convert(source definition.Event) (success bool, result model.Event) {
39 | adapterLayer := &eventAdapterLayer{
40 | EntityImpl: implement.EntityImpl{},
41 | triggerAt: source.TriggerAt(),
42 | canTriggered: source.TriggeredNow,
43 | needClear: source.ClearNow,
44 | callback: source.CallBack,
45 | }
46 |
47 | return true, adapterLayer
48 | }
49 |
50 | func NewEventAdapter() adapter.Adapter[definition.Event, model.Event] {
51 | return EventAdapter{}
52 | }
53 |
--------------------------------------------------------------------------------
/mod/adapter/single.go:
--------------------------------------------------------------------------------
1 | package adapter
2 |
3 | import (
4 | "github.com/sunist-c/genius-invokation-simulator-backend/entity/model"
5 | "github.com/sunist-c/genius-invokation-simulator-backend/mod/definition"
6 | "github.com/sunist-c/genius-invokation-simulator-backend/model/adapter"
7 | )
8 |
9 | var (
10 | eventAdapter = NewEventAdapter()
11 |
12 | cardAdapter = NewCardAdapter()
13 | eventCardAdapter = NewEventCardAdapter()
14 | supportCardAdapter = NewSupportCardAdapter()
15 | equipmentCardAdapter = NewEquipmentCardAdapter()
16 | weaponCardAdapter = NewWeaponCardAdapter()
17 | ruleSetAdapter = NewRuleSetAdapter()
18 |
19 | attackModifierAdapter = NewAttackModifierAdapter()
20 | costModifierAdapter = NewCostModifierAdapter()
21 | healModifierAdapter = NewHealModifierAdapter()
22 | chargeModifierAdapter = NewChargeModifierAdapter()
23 | )
24 |
25 | func GetEventAdapter() adapter.Adapter[definition.Event, model.Event] {
26 | return eventAdapter
27 | }
28 |
29 | func GetCardAdapter() adapter.Adapter[definition.Card, model.Card] {
30 | return cardAdapter
31 | }
32 |
33 | func GetEventCardAdapter() adapter.Adapter[definition.EventCard, model.EventCard] {
34 | return eventCardAdapter
35 | }
36 |
37 | func GetSupportCardAdapter() adapter.Adapter[definition.SupportCard, model.SupportCard] {
38 | return supportCardAdapter
39 | }
40 |
41 | func GetEquipmentCardAdapter() adapter.Adapter[definition.EquipmentCard, model.EquipmentCard] {
42 | return equipmentCardAdapter
43 | }
44 |
45 | func GetWeaponCardAdapter() adapter.Adapter[definition.WeaponCard, model.WeaponCard] {
46 | return weaponCardAdapter
47 | }
48 |
49 | func GetRuleSetAdapter() adapter.Adapter[definition.Rule, model.RuleSet] {
50 | return ruleSetAdapter
51 | }
52 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/sunist-c/genius-invokation-simulator-backend
2 |
3 | go 1.19
4 |
5 | require (
6 | github.com/bytedance/sonic v1.9.1 // indirect
7 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
8 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect
9 | github.com/gin-contrib/sse v0.1.0 // indirect
10 | github.com/gin-gonic/gin v1.9.1 // indirect
11 | github.com/go-playground/locales v0.14.1 // indirect
12 | github.com/go-playground/universal-translator v0.18.1 // indirect
13 | github.com/go-playground/validator/v10 v10.14.0 // indirect
14 | github.com/go-xorm/xorm v0.7.9 // indirect
15 | github.com/goccy/go-json v0.10.2 // indirect
16 | github.com/gorilla/websocket v1.5.0 // indirect
17 | github.com/json-iterator/go v1.1.12 // indirect
18 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect
19 | github.com/leodido/go-urn v1.2.4 // indirect
20 | github.com/mattn/go-isatty v0.0.19 // indirect
21 | github.com/mattn/go-sqlite3 v1.14.16 // indirect
22 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
23 | github.com/modern-go/reflect2 v1.0.2 // indirect
24 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect
25 | github.com/satori/go.uuid v1.2.0 // indirect
26 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
27 | github.com/ugorji/go/codec v1.2.11 // indirect
28 | golang.org/x/arch v0.3.0 // indirect
29 | golang.org/x/crypto v0.9.0 // indirect
30 | golang.org/x/net v0.10.0 // indirect
31 | golang.org/x/sys v0.8.0 // indirect
32 | golang.org/x/text v0.9.0 // indirect
33 | google.golang.org/protobuf v1.30.0 // indirect
34 | gopkg.in/yaml.v2 v2.4.0 // indirect
35 | gopkg.in/yaml.v3 v3.0.1 // indirect
36 | xorm.io/builder v0.3.12 // indirect
37 | xorm.io/core v0.7.3 // indirect
38 | )
39 |
--------------------------------------------------------------------------------
/util/unit_test.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "strconv"
5 | "strings"
6 | "testing"
7 | "time"
8 | )
9 |
10 | func TestUIDGenerator(t *testing.T) {
11 | t.Run("TestUIDGenerator-1", func(t *testing.T) {
12 | timeStamp := GetZeroTimeStamp().Add(time.Second)
13 | macString := "b4:0e:de:fc:1b:ca"
14 | macAddrArr := strings.Split(macString, ":")
15 | macAddr := strings.Join(macAddrArr, "")
16 | mac, _ := strconv.ParseUint(macAddr, 16, 64)
17 | uid := GenerateUID(mac, timeStamp)
18 | if uid != 109951162778600 {
19 | t.Errorf("unexpected uid: %d, expected 109951162778600", uid)
20 | }
21 | })
22 | t.Run("TestUIDGenerator-2", func(t *testing.T) {
23 | timeStamp := GetZeroTimeStamp().Add(time.Second)
24 | macString := "b40edefc1bca"
25 | macAddrArr := strings.Split(macString, ":")
26 | macAddr := strings.Join(macAddrArr, "")
27 | mac, _ := strconv.ParseUint(macAddr, 16, 64)
28 | uid := GenerateUID(mac, timeStamp)
29 | if uid != 109951162778600 {
30 | t.Errorf("unexpected uid: %d, expected 109951162778600", uid)
31 | }
32 | })
33 | t.Run("TestUIDGenerator-3", func(t *testing.T) {
34 | timeStamp := GetZeroTimeStamp().Add(time.Second)
35 | macString := "b40edefc1b:ca"
36 | macAddrArr := strings.Split(macString, ":")
37 | macAddr := strings.Join(macAddrArr, "")
38 | mac, _ := strconv.ParseUint(macAddr, 16, 64)
39 | uid := GenerateUID(mac, timeStamp)
40 | if uid != 109951162778600 {
41 | t.Errorf("unexpected uid: %d, expected 109951162778600", uid)
42 | }
43 | })
44 | }
45 |
46 | func TestRealIDGenerator(t *testing.T) {
47 | t.Run("TestRealIDGenerator", func(t *testing.T) {
48 | realID := GenerateRealID(109951162778600, 1)
49 | if realID != 7205759403858329601 {
50 | t.Errorf("unexpected real-id: %v, expected 7205759403858329601", realID)
51 | }
52 | })
53 | }
54 |
--------------------------------------------------------------------------------
/entity/model/card.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "github.com/sunist-c/genius-invokation-simulator-backend/enum"
5 | )
6 |
7 | type Card interface {
8 | BaseEntity
9 | Type() enum.CardType
10 | Cost() map[enum.ElementType]uint
11 | }
12 |
13 | type EventCard interface {
14 | Card
15 | Event() (event Event)
16 | }
17 |
18 | type SupportCard interface {
19 | Card
20 | Support() (event Event)
21 | }
22 |
23 | type EquipmentCard interface {
24 | Card
25 | EquipmentType() enum.EquipmentType
26 | Modify() (event Event)
27 | }
28 |
29 | type WeaponCard interface {
30 | EquipmentCard
31 | WeaponType() enum.WeaponType
32 | }
33 |
34 | func ConvertToEventCard(original Card) (success bool, result EventCard) {
35 | cardType := original.Type()
36 | if cardType != enum.CardEvent && cardType != enum.CardElementalResonance && cardType != enum.CardFood {
37 | return false, result
38 | } else if convertedCard, convertSuccess := original.(EventCard); !convertSuccess {
39 | return false, result
40 | } else {
41 | return true, convertedCard
42 | }
43 | }
44 |
45 | func ConvertToSupportCard(original Card) (success bool, result SupportCard) {
46 | cardType := original.Type()
47 | if cardType != enum.CardSupport && cardType != enum.CardCompanion && cardType != enum.CardLocation && cardType != enum.CardItem {
48 | return false, result
49 | } else if convertedCard, convertSuccess := original.(SupportCard); !convertSuccess {
50 | return false, result
51 | } else {
52 | return true, convertedCard
53 | }
54 | }
55 |
56 | func ConvertToEquipmentCard(original Card) (success bool, result EquipmentCard) {
57 | cardType := original.Type()
58 | if cardType != enum.CardEquipment && cardType != enum.CardTalent && cardType != enum.CardWeapon && cardType != enum.CardArtifact {
59 | return false, result
60 | } else if convertedCard, convertSuccess := original.(EquipmentCard); !convertSuccess {
61 | return false, result
62 | } else {
63 | return true, convertedCard
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/protocol/http/middleware/interdictor.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "github.com/sunist-c/genius-invokation-simulator-backend/model/kv"
6 | "github.com/sunist-c/genius-invokation-simulator-backend/protocol/http/config"
7 | "time"
8 | )
9 |
10 | func Interdict(ctx *gin.Context, conf config.MiddlewareConfig) {
11 | ctx.Set(conf.InterdictorTraceKey, true)
12 | }
13 |
14 | func NewInterdictor(conf config.MiddlewareConfig) func(ctx *gin.Context) {
15 | limiter := kv.NewSyncMap[kv.Pair[uint, time.Time]]()
16 | return func(ctx *gin.Context) {
17 | success, ip := GetIPTrace(ctx, conf)
18 | if !success {
19 | // 无法成功获取客户端IP,返回BadRequest
20 | ctx.AbortWithStatus(400)
21 | return
22 | }
23 |
24 | if limiter.Exists(ip) {
25 | if pair := limiter.Get(ip); pair.Key() > conf.InterdictorTriggerCount {
26 | if blockedTime := pair.Value(); !blockedTime.IsZero() && blockedTime.Add(time.Second*time.Duration(conf.InterdictorBlockedTime)).After(time.Now()) {
27 | // 失败次数过多且未到解封时间,返回PreconditionFailed
28 | ctx.AbortWithStatus(412)
29 | return
30 | } else {
31 | // 已到解封时间,重置封禁状态
32 | limiter.Remove(ip)
33 | }
34 | }
35 | }
36 |
37 | ctx.Next()
38 |
39 | if f, exist := ctx.Get(conf.InterdictorTraceKey); exist {
40 | if result, ok := f.(bool); ok && result {
41 | if limiter.Exists(ip) {
42 | // 存在标记且被封禁器记录了
43 | pair := limiter.Get(ip)
44 | pair.SetKey(pair.Key() + 1)
45 |
46 | if pair.Key() >= conf.InterdictorTriggerCount {
47 | // 达到封禁标准,封禁
48 | pair.SetValue(time.Now())
49 | }
50 |
51 | limiter.Set(ip, pair)
52 | } else {
53 | // 存在标记但没被封禁器记录,增加记录
54 | limiter.Set(ip, kv.NewPair(uint(1), time.Time{}))
55 | }
56 | } else if limiter.Exists(ip) {
57 | // 标记为假但以前被封禁器记录过,重置状态
58 | limiter.Remove(ip)
59 | }
60 | } else if limiter.Exists(ip) {
61 | // 没有标记但以前被封禁器记录过,重置状态
62 | limiter.Remove(ip)
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/persistence/adapter.go:
--------------------------------------------------------------------------------
1 | package persistence
2 |
3 | import (
4 | "github.com/sunist-c/genius-invokation-simulator-backend/entity/model"
5 | "github.com/sunist-c/genius-invokation-simulator-backend/model/adapter"
6 | )
7 |
8 | type cacheableImpl struct {
9 | id uint64
10 | }
11 |
12 | func (c cacheableImpl) ID() uint64 {
13 | return c.id
14 | }
15 |
16 | func newCacheableImpl(id uint64) cacheableImpl {
17 | return cacheableImpl{id: id}
18 | }
19 |
20 | type CardPersistenceAdapter struct{}
21 |
22 | func (c CardPersistenceAdapter) Convert(source model.Card) (success bool, result Card) {
23 | return true, Card{
24 | Cacheable: newCacheableImpl(source.TypeID()),
25 | Card: source,
26 | }
27 | }
28 |
29 | func NewCardPersistenceAdapter() adapter.Adapter[model.Card, Card] {
30 | return CardPersistenceAdapter{}
31 | }
32 |
33 | type SkillPersistenceAdapter struct{}
34 |
35 | func (s SkillPersistenceAdapter) Convert(source model.Skill) (success bool, result Skill) {
36 | return true, Skill{
37 | Cacheable: newCacheableImpl(source.TypeID()),
38 | Skill: source,
39 | }
40 | }
41 |
42 | func NewSkillPersistenceAdapter() adapter.Adapter[model.Skill, Skill] {
43 | return SkillPersistenceAdapter{}
44 | }
45 |
46 | type EventPersistenceAdapter struct{}
47 |
48 | func (e EventPersistenceAdapter) Convert(source model.Event) (success bool, result Event) {
49 | return true, Event{
50 | Cacheable: newCacheableImpl(source.TypeID()),
51 | Event: source,
52 | }
53 | }
54 |
55 | func NewEventPersistenceAdapter() adapter.Adapter[model.Event, Event] {
56 | return EventPersistenceAdapter{}
57 | }
58 |
59 | type RuleSetPersistenceAdapter struct{}
60 |
61 | func (r RuleSetPersistenceAdapter) Convert(source model.RuleSet) (success bool, result RuleSet) {
62 | return true, RuleSet{
63 | Cacheable: newCacheableImpl(source.ID),
64 | Rule: source,
65 | }
66 | }
67 |
68 | func NewRuleSetAdapter() adapter.Adapter[model.RuleSet, RuleSet] {
69 | return RuleSetPersistenceAdapter{}
70 | }
71 |
--------------------------------------------------------------------------------
/mod/implement/event.go:
--------------------------------------------------------------------------------
1 | package implement
2 |
3 | import (
4 | "github.com/sunist-c/genius-invokation-simulator-backend/enum"
5 | "github.com/sunist-c/genius-invokation-simulator-backend/mod/definition"
6 | "github.com/sunist-c/genius-invokation-simulator-backend/model/context"
7 | )
8 |
9 | type EventImpl struct {
10 | EntityImpl
11 | triggerAt enum.TriggerType
12 | triggerNow func(ctx context.CallbackContext) bool
13 | clearNow func() bool
14 | callback func(ctx *context.CallbackContext)
15 | }
16 |
17 | func (impl *EventImpl) TriggerAt() enum.TriggerType {
18 | return impl.triggerAt
19 | }
20 |
21 | func (impl *EventImpl) TriggeredNow(ctx context.CallbackContext) bool {
22 | return impl.triggerNow(ctx)
23 | }
24 |
25 | func (impl *EventImpl) ClearNow() bool {
26 | return impl.clearNow()
27 | }
28 |
29 | func (impl *EventImpl) CallBack(callbackContext *context.CallbackContext) {
30 | impl.callback(callbackContext)
31 | }
32 |
33 | type EventOptions func(option *EventImpl)
34 |
35 | func WithEventID(id uint16) EventOptions {
36 | return func(option *EventImpl) {
37 | option.InjectTypeID(uint64(id))
38 | }
39 | }
40 |
41 | func WithEventTriggerAt(triggerAt enum.TriggerType) EventOptions {
42 | return func(option *EventImpl) {
43 | option.triggerAt = triggerAt
44 | }
45 | }
46 |
47 | func WithEventTriggerNow(triggerNow func(ctx context.CallbackContext) bool) EventOptions {
48 | return func(option *EventImpl) {
49 | option.triggerNow = triggerNow
50 | }
51 | }
52 |
53 | func WithEventClearNow(clearNow func() bool) EventOptions {
54 | return func(option *EventImpl) {
55 | option.clearNow = clearNow
56 | }
57 | }
58 |
59 | func WithEventCallback(callback func(ctx *context.CallbackContext)) EventOptions {
60 | return func(option *EventImpl) {
61 | option.callback = callback
62 | }
63 | }
64 |
65 | func NewEventWithOpts(options ...EventOptions) definition.Event {
66 | impl := &EventImpl{}
67 | for _, option := range options {
68 | option(impl)
69 | }
70 |
71 | return impl
72 | }
73 |
--------------------------------------------------------------------------------
/persistence/interface.go:
--------------------------------------------------------------------------------
1 | package persistence
2 |
3 | import "time"
4 |
5 | // FactoryPersistenceRecord 抽象工厂的持久化结构记录
6 | type FactoryPersistenceRecord struct {
7 | ID uint64 `json:"id"`
8 | UID string `json:"uid"`
9 | }
10 |
11 | // FactoryPersistence 持久化接口,抽象工厂集合的持久化封装
12 | type FactoryPersistence[T any] interface {
13 | Serve(flushFrequency time.Duration, flushPath, flushFile string, errChan chan error)
14 | Exit()
15 | Load(filePath string) (err error)
16 | QueryByID(id uint64) (has bool, result Factory[T])
17 | QueryByUID(uid string) (has bool, result Factory[T])
18 | Register(ctor func() T) (success bool)
19 | Flush(flushPath string, flushFile string) (err error)
20 | }
21 |
22 | // Increasable 可增长的,用于数据库主键
23 | type Increasable interface {
24 | int | uint | byte | rune | int8 | int16 | int64 | uint16 | uint32 | uint64
25 | }
26 |
27 | // DatabasePersistence 数据库持久化
28 | type DatabasePersistence[PK Increasable, T any] interface {
29 | QueryByID(id PK) (has bool, result T)
30 | UpdateByID(id PK, newEntity T) (success bool)
31 | InsertOne(entity *T) (success bool, result *T)
32 | DeleteOne(id PK) (success bool)
33 | FindOne(condition T) (has bool, result T)
34 | Find(condition T) (results []T)
35 | }
36 |
37 | // MemoryCache 内存缓存,不进行持久化
38 | type MemoryCache[PK comparable, T any] interface {
39 | QueryByID(id PK) (has bool, result T)
40 | UpdateByID(id PK, newEntity T) (success bool)
41 | InsertOne(id PK, entity T) (success bool)
42 | DeleteOne(id PK) (success bool)
43 | }
44 |
45 | // TimingMemoryCache 带超时系统的内存缓存,不进行持久化,类redis
46 | type TimingMemoryCache[PK comparable, T any] interface {
47 | QueryByID(id PK) (has bool, result T, timeoutAt time.Time)
48 | UpdateByID(id PK, entity T) (success bool, timeoutAt time.Time)
49 | RefreshByID(id PK, timeout time.Duration) (success bool, timeoutAt time.Time)
50 | InsertOne(id PK, entity T, timeout time.Duration) (success bool, timeoutAt time.Time)
51 | DeleteByID(id PK) (success bool)
52 | Serve(proactivelyCleanTime time.Duration, proactivelyCleanIndex float64)
53 | Exit()
54 | }
55 |
--------------------------------------------------------------------------------
/entity/model/rule.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "github.com/sunist-c/genius-invokation-simulator-backend/enum"
5 | "github.com/sunist-c/genius-invokation-simulator-backend/model/context"
6 | )
7 |
8 | type ReactionCalculator interface {
9 | // ReactionCalculate 计算当前角色身上附着的元素之间能否发生反应,返回反应类型和剩余元素
10 | ReactionCalculate([]enum.ElementType) (reaction enum.Reaction, elementRemains []enum.ElementType)
11 |
12 | // DamageCalculate 根据反应类型计算对应的伤害修正
13 | DamageCalculate(reaction enum.Reaction, targetCharacter Character, ctx *context.DamageContext)
14 |
15 | // EffectCalculate 根据反应类型计算对应的反应效果
16 | EffectCalculate(reaction enum.Reaction, targetPlayer Player) (ctx *context.CallbackContext)
17 |
18 | // Attach 尝试让新元素附着在现有元素集合内,此时不触发元素反应,返回尝试附着后的元素集合
19 | Attach(originalElements []enum.ElementType, newElement enum.ElementType) (resultElements []enum.ElementType)
20 |
21 | // Relative 判断某种反应是否是某元素的相关反应
22 | Relative(reaction enum.Reaction, relativeElement enum.ElementType) bool
23 | }
24 |
25 | type VictorCalculator interface {
26 | // CalculateVictors 计算游戏的胜利者
27 | CalculateVictors(players []Player) (has bool, victors []Player)
28 | }
29 |
30 | type GameOptions struct {
31 | ReRollTimes uint // ReRollTimes 所有玩家的基础可重掷次数
32 | StaticCost map[enum.ElementType]uint // StaticCost 所有玩家的基础固定持有骰子
33 | RollAmount uint // RollAmount 所有玩家的投掷阶段生成元素骰子数量
34 | GetCards uint // GetCards 所有玩家在回合开始时可以获得的卡牌数量
35 | SwitchCost map[enum.ElementType]uint // SwitchCost 切换角色所需要的消费
36 | }
37 |
38 | var (
39 | nullReactionCalculator ReactionCalculator = nil
40 | nullVictorCalculator VictorCalculator = nil
41 | )
42 |
43 | type RuleSet struct {
44 | ID uint64
45 | ReactionCalculator ReactionCalculator
46 | VictorCalculator VictorCalculator
47 | }
48 |
49 | func (r RuleSet) ImplementationCheck() bool {
50 | if r.ReactionCalculator == nullReactionCalculator {
51 | return false
52 | }
53 |
54 | if r.VictorCalculator == nullVictorCalculator {
55 | return false
56 | }
57 |
58 | return true
59 | }
60 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Document
6 |
7 |
8 |
9 |
10 |
11 |
12 |
19 |
22 |
23 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/protocol/http/middleware/authenticator.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "github.com/sunist-c/genius-invokation-simulator-backend/persistence"
6 | "github.com/sunist-c/genius-invokation-simulator-backend/protocol/http/config"
7 | "github.com/sunist-c/genius-invokation-simulator-backend/util"
8 | "strconv"
9 | "time"
10 | )
11 |
12 | // AttachToken 将Token信息附加给响应
13 | func AttachToken(ctx *gin.Context, conf config.MiddlewareConfig, player uint64) (success bool) {
14 | uuid := GetUUID(ctx, conf)
15 | ok, ip := GetIPTrace(ctx, conf)
16 | if !ok {
17 | return false
18 | }
19 | tokenID, token := util.GenerateMD5(strconv.Itoa(int(ip))), util.GenerateMD5(uuid)
20 | if success, _ = persistence.TokenPersistence.InsertOne(token, persistence.Token{UID: player, ID: tokenID}, time.Second*time.Duration(conf.TokenRefreshTime)); !success {
21 | return false
22 | } else {
23 | ctx.SetCookie(conf.TokenIDKey, tokenID, int(conf.TokenRefreshTime), "/", conf.CookieDomain, false, true)
24 | ctx.SetCookie(conf.TokenKey, token, int(conf.TokenRefreshTime), "/", conf.CookieDomain, false, true)
25 | return true
26 | }
27 | }
28 |
29 | // GetToken 获取Cookie里的Token信息
30 | func GetToken(ctx *gin.Context, conf config.MiddlewareConfig) (success bool, token persistence.Token) {
31 | if result, err := ctx.Cookie(conf.TokenKey); err != nil {
32 | return false, token
33 | } else if has, tokenStruct, _ := persistence.TokenPersistence.QueryByID(result); !has {
34 | return false, token
35 | } else {
36 | return true, tokenStruct
37 | }
38 | }
39 |
40 | // NewAuthenticator 新建一个认证器,只有Cookie中的Token和ID正确时才会放行,但是没有具体处理是否有权限
41 | func NewAuthenticator(conf config.MiddlewareConfig) func(ctx *gin.Context) {
42 | return func(ctx *gin.Context) {
43 | if tokenID, err := ctx.Cookie(conf.TokenIDKey); err != nil {
44 | ctx.AbortWithStatus(403)
45 | } else if token, err := ctx.Cookie(conf.TokenKey); err != nil {
46 | ctx.AbortWithStatus(403)
47 | } else if has, result, timeoutAt := persistence.TokenPersistence.QueryByID(token); !has {
48 | ctx.AbortWithStatus(403)
49 | } else if result.ID != tokenID {
50 | ctx.AbortWithStatus(403)
51 | } else if time.Now().Add(time.Duration(conf.TokenRemainingTime) * time.Second).After(timeoutAt) {
52 | persistence.TokenPersistence.RefreshByID(token, time.Second*time.Duration(conf.TokenRefreshTime))
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/model/modifier/benchmark_test.go:
--------------------------------------------------------------------------------
1 | package modifier
2 |
3 | import "testing"
4 |
5 | // BenchmarkTestContextRemove
6 | func BenchmarkTestContextRemove(b *testing.B) {
7 | add := func(ctx *Context[int]) {
8 | *ctx.data += 1
9 | }
10 | addTwice := func(ctx *Context[int]) {
11 | *ctx.data += 1
12 | ctx.Next()
13 | *ctx.data += 1
14 | }
15 | addBack := func(ctx *Context[int]) {
16 | ctx.Next()
17 | *ctx.data += 1
18 | }
19 | handlers := NewChain[int]()
20 | for j := uint64(0); j <= 128; j++ {
21 | switch j % 3 {
22 | case 0:
23 | handlers.Append(newModifier(j, add))
24 | case 1:
25 | handlers.Append(newModifier(j, addTwice))
26 | case 2:
27 | handlers.Append(newModifier(j, addBack))
28 | }
29 | }
30 | b.ResetTimer()
31 |
32 | for i := 0; i < b.N; i++ {
33 | handlers.Remove(128)
34 | }
35 | }
36 |
37 | // BenchmarkTestContextAppend
38 | func BenchmarkTestContextAppend(b *testing.B) {
39 | add := func(ctx *Context[int]) {
40 | *ctx.data += 1
41 | }
42 | addTwice := func(ctx *Context[int]) {
43 | *ctx.data += 1
44 | ctx.Next()
45 | *ctx.data += 1
46 | }
47 | addBack := func(ctx *Context[int]) {
48 | ctx.Next()
49 | *ctx.data += 1
50 | }
51 | handlers := NewChain[int]()
52 | for j := uint64(0); j <= 128; j++ {
53 | switch j % 3 {
54 | case 0:
55 | handlers.Append(newModifier(j, add))
56 | case 1:
57 | handlers.Append(newModifier(j, addTwice))
58 | case 2:
59 | handlers.Append(newModifier(j, addBack))
60 | }
61 | }
62 | b.ResetTimer()
63 | for i := 0; i < b.N; i++ {
64 | handlers.Append(newModifier(129, add))
65 | }
66 | }
67 |
68 | // BenchmarkTestContextExecute
69 | func BenchmarkTestContextExecute(b *testing.B) {
70 | add := func(ctx *Context[int]) {
71 | *ctx.data += 1
72 | }
73 | addTwice := func(ctx *Context[int]) {
74 | *ctx.data += 1
75 | ctx.Next()
76 | *ctx.data += 1
77 | }
78 | addBack := func(ctx *Context[int]) {
79 | ctx.Next()
80 | *ctx.data += 1
81 | }
82 | handlers := NewChain[int]()
83 | for j := uint64(0); j < 1; j++ {
84 | switch j % 3 {
85 | case 0:
86 | handlers.Append(newModifier(j, add))
87 | case 1:
88 | handlers.Append(newModifier(j, addTwice))
89 | case 2:
90 | handlers.Append(newModifier(j, addBack))
91 | }
92 | }
93 | b.ResetTimer()
94 |
95 | for i := 0; i < b.N; i++ {
96 | data := 0
97 | handlers.Execute(&data)
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/protocol/http/service/roominfo.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "github.com/sunist-c/genius-invokation-simulator-backend/persistence"
6 | "github.com/sunist-c/genius-invokation-simulator-backend/protocol/http"
7 | "github.com/sunist-c/genius-invokation-simulator-backend/protocol/http/middleware"
8 | "github.com/sunist-c/genius-invokation-simulator-backend/protocol/http/util"
9 | )
10 |
11 | var (
12 | roomInfoRouter *gin.RouterGroup
13 | roomInfoCache map[uint64]bool
14 | )
15 |
16 | func initRoomInfoService() {
17 | roomInfoRouter = http.RegisterServices("/room")
18 | roomInfoCache = map[uint64]bool{}
19 | for i := uint64(0); i < serviceConfig.MaxRooms; i++ {
20 | roomInfoCache[i] = false
21 | }
22 |
23 | roomInfoRouter.Use(
24 | append(
25 | http.EngineMiddlewares,
26 | middleware.NewQPSLimiter(middlewareConfig),
27 | )...,
28 | )
29 |
30 | roomInfoRouter.GET("",
31 | listRoomServiceHandler(),
32 | )
33 | roomInfoRouter.GET(":room_id",
34 | queryRoomServiceHandler(),
35 | )
36 | }
37 |
38 | type ListRoomResponse struct {
39 | Rooms []persistence.RoomInfo `json:"rooms"`
40 | }
41 |
42 | type QueryRoomResponse struct {
43 | RoomInfo persistence.RoomInfo `json:"room_info"`
44 | }
45 |
46 | func listRoomServiceHandler() func(ctx *gin.Context) {
47 | return func(ctx *gin.Context) {
48 | response := &ListRoomResponse{Rooms: []persistence.RoomInfo{}}
49 |
50 | // 查询RoomInfo状态
51 | for roomID, roomValid := range roomInfoCache {
52 | // 将可用的房间进行查询
53 | if roomValid {
54 | if success, appendRoom := persistence.RoomInfoPersistence.QueryByID(roomID); !success {
55 | // 查找RoomInfo失败,InternalError
56 | ctx.AbortWithStatus(500)
57 | return
58 | } else {
59 | // 查找RoomInfo成功,加入到响应中
60 | response.Rooms = append(response.Rooms, appendRoom)
61 | }
62 | }
63 | }
64 |
65 | // 查询完毕,Success
66 | ctx.JSON(200, response)
67 | }
68 | }
69 |
70 | func queryRoomServiceHandler() func(ctx *gin.Context) {
71 | return func(ctx *gin.Context) {
72 | if existRoomID, roomID := util.QueryPathUint64(ctx, ":room_id"); !existRoomID {
73 | // 缺少必要的URL参数,BadRequest
74 | ctx.AbortWithStatus(400)
75 | } else if roomValid, existRoom := roomInfoCache[roomID]; !existRoom || !roomValid {
76 | // 请求的房间不存在或无效,NotFound
77 | ctx.AbortWithStatus(404)
78 | } else if existRoomInfo, roomInfo := persistence.RoomInfoPersistence.QueryByID(roomID); !existRoomInfo {
79 | // 查询房间信息失败,InternalError
80 | ctx.AbortWithStatus(500)
81 | } else {
82 | // 查询成功,Success
83 | response := QueryRoomResponse{RoomInfo: roomInfo}
84 | ctx.JSON(200, response)
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/mod/implement/character.go:
--------------------------------------------------------------------------------
1 | package implement
2 |
3 | import (
4 | "github.com/sunist-c/genius-invokation-simulator-backend/enum"
5 | "github.com/sunist-c/genius-invokation-simulator-backend/mod/definition"
6 | )
7 |
8 | type CharacterImpl struct {
9 | EntityImpl
10 | name string
11 | affiliation enum.Affiliation
12 | vision enum.ElementType
13 | weapon enum.WeaponType
14 | skills []definition.Skill
15 | hp uint
16 | mp uint
17 | }
18 |
19 | func (c CharacterImpl) Name() string {
20 | return c.name
21 | }
22 |
23 | func (c CharacterImpl) Affiliation() enum.Affiliation {
24 | return c.affiliation
25 | }
26 |
27 | func (c CharacterImpl) Vision() enum.ElementType {
28 | return c.vision
29 | }
30 |
31 | func (c CharacterImpl) Weapon() enum.WeaponType {
32 | return c.weapon
33 | }
34 |
35 | func (c CharacterImpl) Skills() []definition.Skill {
36 | return c.skills
37 | }
38 |
39 | func (c CharacterImpl) HP() uint {
40 | return c.hp
41 | }
42 |
43 | func (c CharacterImpl) MP() uint {
44 | return c.mp
45 | }
46 |
47 | func WithCharacterID(id uint16) CharacterOptions {
48 | return func(option *CharacterImpl) {
49 | option.InjectTypeID(uint64(id))
50 | }
51 | }
52 |
53 | func WithCharacterName(name string) CharacterOptions {
54 | return func(option *CharacterImpl) {
55 | option.name = name
56 | }
57 | }
58 |
59 | func WithCharacterAffiliation(affiliation enum.Affiliation) CharacterOptions {
60 | return func(option *CharacterImpl) {
61 | option.affiliation = affiliation
62 | }
63 | }
64 |
65 | func WithCharacterVision(vision enum.ElementType) CharacterOptions {
66 | return func(option *CharacterImpl) {
67 | option.vision = vision
68 | }
69 | }
70 |
71 | func WithCharacterWeapon(weapon enum.WeaponType) CharacterOptions {
72 | return func(option *CharacterImpl) {
73 | option.weapon = weapon
74 | }
75 | }
76 |
77 | func WithCharacterSkills(skills ...definition.Skill) CharacterOptions {
78 | return func(option *CharacterImpl) {
79 | option.skills = skills
80 | }
81 | }
82 |
83 | func WithCharacterHP(hp uint) CharacterOptions {
84 | return func(option *CharacterImpl) {
85 | option.hp = hp
86 | }
87 | }
88 |
89 | func WithCharacterMP(mp uint) CharacterOptions {
90 | return func(option *CharacterImpl) {
91 | option.mp = mp
92 | }
93 | }
94 |
95 | type CharacterOptions func(option *CharacterImpl)
96 |
97 | func NewCharacterWithOpts(opts ...CharacterOptions) definition.Character {
98 | character := &CharacterImpl{}
99 | for _, opt := range opts {
100 | opt(character)
101 | }
102 |
103 | return character
104 | }
105 |
--------------------------------------------------------------------------------
/protocol/http/service/localization.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "github.com/sunist-c/genius-invokation-simulator-backend/enum"
6 | "github.com/sunist-c/genius-invokation-simulator-backend/persistence"
7 | "github.com/sunist-c/genius-invokation-simulator-backend/protocol/http"
8 | "github.com/sunist-c/genius-invokation-simulator-backend/protocol/http/message"
9 | "github.com/sunist-c/genius-invokation-simulator-backend/protocol/http/middleware"
10 | "github.com/sunist-c/genius-invokation-simulator-backend/protocol/http/util"
11 | )
12 |
13 | var (
14 | localizationRouter *gin.RouterGroup
15 | )
16 |
17 | func initLocalizeService() {
18 | localizationRouter = http.RegisterServices("/localization")
19 |
20 | localizationRouter.Use(
21 | append(
22 | http.EngineMiddlewares,
23 | middleware.NewQPSLimiter(middlewareConfig),
24 | )...,
25 | )
26 | localizationRouter.GET(
27 | "/language_pack/:id",
28 | queryLanguagePackServiceHandler(),
29 | )
30 | localizationRouter.GET(
31 | "/translate",
32 | translateServiceServiceHandler(),
33 | )
34 | }
35 |
36 | func queryLanguagePackServiceHandler() func(ctx *gin.Context) {
37 | return func(ctx *gin.Context) {
38 | if exist, result := util.QueryPath(ctx, ":id"); !exist {
39 | ctx.AbortWithStatus(400)
40 | } else if has, record := persistence.LocalizationPersistence.QueryByID(result); !has {
41 | ctx.AbortWithStatus(404)
42 | } else {
43 | response := message.LocalizationQueryResponse{LanguagePack: record.Pack()}
44 | ctx.JSON(200, response)
45 | }
46 | }
47 | }
48 |
49 | func translateServiceServiceHandler() func(ctx *gin.Context) {
50 | return func(ctx *gin.Context) {
51 | var (
52 | exist bool
53 | languagePack string
54 | destLanguage int
55 | request message.TranslationRequest
56 | )
57 | if exist, languagePack = util.QueryPath(ctx, "language_package"); !exist {
58 | ctx.AbortWithStatus(400)
59 | } else if exist, destLanguage = util.QueryPathInt(ctx, "target_language"); !exist {
60 | ctx.AbortWithStatus(400)
61 | } else if !util.BindJson(ctx, &request) {
62 | ctx.AbortWithStatus(400)
63 | } else {
64 | if has, dictionary := persistence.LocalizationPersistence.QueryByID(languagePack); !has {
65 | ctx.AbortWithStatus(404)
66 | } else {
67 | response := message.TranslationResponse{Translation: map[string]string{}}
68 | language := enum.Language(destLanguage)
69 | for _, word := range request.Words {
70 | if ok, result := dictionary.Translate(word, language); ok {
71 | response.Translation[word] = result
72 | }
73 | }
74 | ctx.JSON(200, response)
75 | }
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/persistence/sqlite.go:
--------------------------------------------------------------------------------
1 | package persistence
2 |
3 | import (
4 | "github.com/go-xorm/xorm"
5 | _ "github.com/mattn/go-sqlite3"
6 | )
7 |
8 | var (
9 | sqlite3DB *xorm.Engine = nil
10 | )
11 |
12 | type sqliteTable[PK Increasable, T any] struct {
13 | session *xorm.Session
14 | errChan chan error
15 | }
16 |
17 | func (s *sqliteTable[PK, T]) QueryByID(id PK) (has bool, result T) {
18 | if has, err := s.session.ID(id).Get(&result); err != nil {
19 | s.errChan <- err
20 | return false, result
21 | } else if !has {
22 | return false, result
23 | } else {
24 | return true, result
25 | }
26 | }
27 |
28 | func (s *sqliteTable[PK, T]) UpdateByID(id PK, newEntity T) (success bool) {
29 | var condition T
30 | if has, err := s.session.ID(id).Get(&condition); err != nil {
31 | s.errChan <- err
32 | return false
33 | } else if !has {
34 | return false
35 | } else if _, err = s.session.ID(id).Update(newEntity); err != nil {
36 | s.errChan <- err
37 | return false
38 | } else {
39 | return true
40 | }
41 | }
42 |
43 | func (s *sqliteTable[PK, T]) InsertOne(entity *T) (success bool, result *T) {
44 | if _, err := s.session.InsertOne(entity); err != nil {
45 | s.errChan <- err
46 | return false, nil
47 | } else {
48 | return true, entity
49 | }
50 | }
51 |
52 | func (s *sqliteTable[PK, T]) DeleteOne(id PK) (success bool) {
53 | var condition T
54 | if exist, err := s.session.ID(id).Get(&condition); err != nil {
55 | s.errChan <- err
56 | return false
57 | } else if !exist {
58 | return false
59 | } else if _, err = s.session.ID(id).Delete(condition); err != nil {
60 | s.errChan <- err
61 | return false
62 | } else {
63 | return true
64 | }
65 | }
66 |
67 | func (s *sqliteTable[PK, T]) FindOne(condition T) (has bool, result T) {
68 | var results []T
69 | if err := s.session.Find(&results, condition); err != nil {
70 | s.errChan <- err
71 | return false, result
72 | } else if len(results) != 1 {
73 | return false, result
74 | } else {
75 | return true, results[0]
76 | }
77 | }
78 |
79 | func (s *sqliteTable[PK, T]) Find(condition T) (results []T) {
80 | if err := s.session.Find(&results, condition); err != nil {
81 | s.errChan <- err
82 | return []T{}
83 | } else {
84 | return results
85 | }
86 | }
87 |
88 | func newDatabasePersistence[PK Increasable, T any](errChan chan error) (success bool, persistence DatabasePersistence[PK, T]) {
89 | var entity T
90 | if err := sqlite3DB.Sync2(entity); err != nil {
91 | errChan <- err
92 | return false, persistence
93 | } else {
94 | table := &sqliteTable[PK, T]{
95 | session: sqlite3DB.Table(entity),
96 | errChan: errChan,
97 | }
98 | return true, table
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/docs/development/api/README.md:
--------------------------------------------------------------------------------
1 | 这里是GISB的API文档,此文档为前后端开发人员提供关于GISB的 `HTTP` 服务的说明与解释
2 |
3 | # 1. 状态码定义
4 |
5 | GISB的后端使用HTTP状态码来表示请求的响应结果,大部分情况下,非 `200(Success)` 状态码都不会返回除状态码的任何信息
6 |
7 | 下面是GISB的HTTP服务对其返回状态码的说明:
8 |
9 | **200(Success)**
10 |
11 | 此状态表明HTTP请求合法,且服务端响应过程正常,且返回数据正常
12 |
13 | **400(BadRequest)**
14 |
15 | 此状态表明HTTP请求非法,您可以试着分析您的请求,是否将必须的参数提供完整提供,且请求体的格式正确无误
16 |
17 | **403(Forbidden)**
18 |
19 | 此状态表明HTTP请求合法,但发起者对所请求的资源不具备访问权限,您可以试着分析您的请求,是否将鉴权相关的参数/cookie完整提供,或是请求的资源是否正确
20 |
21 | **404(NotFound)**
22 |
23 | 此状态表明HTTP请求合法,但服务端不持有所请求资源,您可以检查您访问的资源是否正确
24 |
25 | **412(PreconditionFailed)**
26 |
27 | 此状态表明HTTP请求合法,但发起者被服务器所限制,一般是由于您的请求频率过快或认证失败次数过多导致的,您可以等待一段时间后重新发起请求
28 |
29 | **500(InternalError)**
30 |
31 | 此状态表明HTTP请求合法,但服务器在响应请求的过程中出现了错误,您可以将此错误反馈给服务器管理人员或本项目的开发者以寻求解决方法
32 |
33 | # 2. 配置说明
34 |
35 | GISB可以在启动前对其进行配置,此处对其进行说明与解释,配置的声明如下:
36 |
37 | ```go
38 | type MiddlewareConfig struct {
39 | UUIDKey string `json:"uuid_key" yaml:"uuid_key" xml:"uuid_key"`
40 | IPTranceKey string `json:"ip_trace_key" yaml:"ip_trance_key" xml:"ip_trance_key"`
41 | InterdictorTraceKey string `json:"interdictor_trace_key" yaml:"interdictor_trace_key" xml:"interdictor_trace_key"`
42 | InterdictorBlockedTime uint `json:"interdictor_blocked_time" yaml:"interdictor_blocked_time" xml:"interdictor_blocked_time"`
43 | InterdictorTriggerCount uint `json:"interdictor_trigger_count" yaml:"interdictor_trigger_count" xml:"interdictor_trigger_count"`
44 | QPSLimitTime uint `json:"qps_limit_time" yaml:"qps_limit_time" xml:"qps_limit_time"`
45 | TokenIDKey string `json:"token_id_key" yaml:"token_id_key" xml:"token_id_key"`
46 | TokenKey string `json:"token_key" yaml:"token_key" xml:"token_key"`
47 | TokenRefreshTime uint `json:"token_refresh_time" yaml:"token_refresh_time" xml:"token_refresh_time"`
48 | TokenRemainingTime uint `json:"token_remaining_time" yaml:"token_remaining_time" xml:"token_remaining_time"`
49 | CookieDomain string `json:"cookie_domain" yaml:"cookie_domain" xml:"cookie_domain"`
50 | }
51 | ```
52 |
53 | 默认的配置如下:
54 |
55 | ```go
56 | var (
57 | config = &EngineConfig{
58 | Middleware: MiddlewareConfig{
59 | UUIDKey: "uuid",
60 | IPTranceKey: "ip",
61 | InterdictorTraceKey: "interdicted",
62 | InterdictorBlockedTime: 600,
63 | InterdictorTriggerCount: 5,
64 | QPSLimitTime: 1,
65 | TokenIDKey: "gisb_token_id",
66 | TokenKey: "gisb_token",
67 | TokenRefreshTime: 7200,
68 | TokenRemainingTime: 1800,
69 | CookieDomain: "localhost",
70 | },
71 | Service: ServiceConfig{
72 | MaxRooms: 100,
73 | },
74 | }
75 | }
76 | )
77 | ```
78 |
--------------------------------------------------------------------------------
/entity/carddeck.go:
--------------------------------------------------------------------------------
1 | package entity
2 |
3 | import (
4 | "github.com/sunist-c/genius-invokation-simulator-backend/entity/model"
5 | "github.com/sunist-c/genius-invokation-simulator-backend/enum"
6 | "math/rand"
7 | "time"
8 | )
9 |
10 | var (
11 | random = rand.New(rand.NewSource(time.Now().UnixMilli()))
12 | )
13 |
14 | // CardDeck 牌堆,记录顺序的是一个数组,正常情况下已取出的牌永远在队列的一端
15 | type CardDeck struct {
16 | cards map[uint64]model.Card
17 | used map[uint64]bool
18 | queue []uint64
19 | offset int
20 | remain uint
21 | }
22 |
23 | // arrange 整理CardDeck中offset之后的部分
24 | func (c *CardDeck) arrange() {
25 | for i := c.offset; i < len(c.queue); i++ {
26 | if c.used[c.queue[i]] {
27 | c.queue[c.offset], c.queue[i] = c.queue[i], c.queue[c.offset]
28 | c.offset++
29 | c.remain--
30 | }
31 | }
32 | }
33 |
34 | // takeOne 将CardDeck的队列中第index张牌标记为已取出
35 | func (c *CardDeck) takeOne(index int) {
36 | c.used[c.queue[index]] = true
37 | c.queue[index], c.queue[c.offset] = c.queue[c.offset], c.queue[index]
38 | c.offset++
39 | c.remain--
40 | }
41 |
42 | // Shuffle 将CardDeck中的未取出部分进行洗牌
43 | func (c *CardDeck) Shuffle() {
44 | random.Shuffle(len(c.queue)-c.offset, func(i, j int) {
45 | c.queue[i+c.offset], c.queue[j+c.offset] = c.queue[j+c.offset], c.queue[i+c.offset]
46 | })
47 | }
48 |
49 | // GetOne 从CardDeck中取出一张牌
50 | func (c *CardDeck) GetOne() (result model.Card, success bool) {
51 | if c.remain != 0 {
52 | for i := c.offset; i < len(c.queue); i++ {
53 | if !c.used[c.queue[i]] {
54 | result = c.cards[c.queue[i]]
55 | c.takeOne(i)
56 | return result, true
57 | }
58 | }
59 | }
60 |
61 | return nil, false
62 | }
63 |
64 | // FindOne 从CardDeck中取出一张指定类型的牌
65 | func (c *CardDeck) FindOne(cardType enum.CardType) (result model.Card, success bool) {
66 | for i := c.offset; i < len(c.queue); i++ {
67 | if !c.used[c.queue[i]] && c.cards[c.queue[i]].Type() == cardType {
68 | result = c.cards[c.queue[i]]
69 | c.takeOne(i)
70 | return result, true
71 | }
72 | }
73 |
74 | return nil, false
75 | }
76 |
77 | // Reset 将牌堆中除了holding之外的牌全部标记为未取出,此方法没有洗牌逻辑
78 | func (c *CardDeck) Reset(holding []uint64) {
79 | for card := range c.used {
80 | c.used[card] = false
81 | }
82 |
83 | c.remain = uint(len(c.queue))
84 | c.offset = 0
85 | for _, id := range holding {
86 | c.used[id] = true
87 | }
88 |
89 | c.arrange()
90 | }
91 |
92 | // Remain 获取CardDeck还可以获取多少张牌
93 | func (c CardDeck) Remain() uint {
94 | return c.remain
95 | }
96 |
97 | func NewCardDeck(cards []model.Card) *CardDeck {
98 | cardDeck := &CardDeck{
99 | cards: map[uint64]model.Card{},
100 | used: map[uint64]bool{},
101 | queue: []uint64{},
102 | offset: 0,
103 | remain: 0,
104 | }
105 |
106 | for _, card := range cards {
107 | cardDeck.cards[card.TypeID()] = card
108 | cardDeck.used[card.TypeID()] = false
109 | cardDeck.queue = append(cardDeck.queue, card.TypeID())
110 | cardDeck.remain++
111 | }
112 |
113 | return cardDeck
114 | }
115 |
--------------------------------------------------------------------------------
/persistence/model.go:
--------------------------------------------------------------------------------
1 | package persistence
2 |
3 | import (
4 | "github.com/sunist-c/genius-invokation-simulator-backend/entity/model"
5 | "github.com/sunist-c/genius-invokation-simulator-backend/enum"
6 | "github.com/sunist-c/genius-invokation-simulator-backend/protocol/websocket/message"
7 | "time"
8 | )
9 |
10 | // Card 被持久化模块托管的Card工厂的产品
11 | type Card struct {
12 | Cacheable
13 | Card model.Card
14 | }
15 |
16 | // Skill 被持久化模块托管的Skill工厂的产品
17 | type Skill struct {
18 | Cacheable
19 | Skill model.Skill
20 | }
21 |
22 | // RuleSet 被持久化模块托管的RuleSet工厂的产品
23 | type RuleSet struct {
24 | Cacheable
25 | Rule model.RuleSet
26 | }
27 |
28 | // Summon 被持久化模块托管的Summon工厂的产品
29 | type Summon struct {
30 | Cacheable
31 | // todo: add summon interfaces
32 | }
33 |
34 | // Event 被持久化模块托管的Event工厂的产品
35 | type Event struct {
36 | Cacheable
37 | Event model.Event
38 | }
39 |
40 | // Character 被持久化模块托管的CharacterInfo工厂的产品
41 | type Character struct {
42 | Cacheable
43 | TypeID uint64
44 | Affiliation enum.Affiliation
45 | Vision enum.ElementType
46 | Weapon enum.WeaponType
47 | MaxHP uint
48 | MaxMP uint
49 | Skills []uint64
50 | }
51 |
52 | func (c Character) ID() uint64 { return c.TypeID }
53 |
54 | // Player 被持久化模块托管的Player信息
55 | type Player struct {
56 | UID uint64 `xorm:"pk autoincr notnull unique index"` // UID Player的UID,主键
57 | NickName string `xorm:"notnull varchar(64)"` // NickName Player的昵称
58 | Password string `xorm:"notnull varchar(64)"` // Password Player的密码Hash
59 | }
60 |
61 | // CardDeck 被持久化模块托管的CardDeck信息
62 | type CardDeck struct {
63 | ID uint64 `xorm:"pk autoincr notnull unique index" json:"id"` // ID CardDeck的记录ID,主键
64 | OwnerUID uint64 `xorm:"notnull index" json:"owner_uid"` // OwnerUID CardDeck的持有者
65 | RequiredPackages []string `xorm:"notnull json" json:"required_packages"` // RequiredPackages CardDeck需要的包
66 | Cards []uint64 `xorm:"notnull json" json:"cards"` // Cards CardDeck包含的卡组
67 | Characters []uint64 `xorm:"notnull json" json:"characters"` // Characters CardDeck包含的角色
68 | }
69 |
70 | // Token 被持久化模块托管的Token缓存
71 | type Token struct {
72 | UID uint64 // UID Token持有者的UID
73 | ID string // ID Token的ID
74 | }
75 |
76 | // RoomInfo 被持久化模块托管的RoomInfo缓存
77 | type RoomInfo struct {
78 | RoomID uint64 `json:"room_id"`
79 | CreatedAt time.Time `json:"created_at"`
80 | CreatorID uint64 `json:"creator_id"`
81 | Token string `json:"token"`
82 | Players []uint64 `json:"players"`
83 | Viewers uint64 `json:"viewers"`
84 | RequiredPackages []string `json:"required_packages"`
85 | GameOptions message.GameOptions `json:"game_options"`
86 | }
87 |
88 | // ModInfo 被持久化模块托管的ModInfo缓存
89 | type ModInfo struct {
90 | PackageName string `json:"package_name"`
91 | Events []uint `json:"events"`
92 | Characters []uint `json:"characters"`
93 | Cards []uint `json:"cards"`
94 | Skills []uint `json:"skills"`
95 | Summons []uint `json:"summons"`
96 | }
97 |
--------------------------------------------------------------------------------
/util/generate.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "crypto/md5"
5 | "fmt"
6 | uuid "github.com/satori/go.uuid"
7 | "reflect"
8 | "time"
9 | "unsafe"
10 | )
11 |
12 | type UintLike interface {
13 | uint | uint8 | uint16 | uint32 | uint64
14 | }
15 |
16 | // GenerateUUID 生成一个UUID,长度为36
17 | func GenerateUUID() string {
18 | return uuid.Must(uuid.NewV4(), nil).String()
19 | }
20 |
21 | // GenerateTypeID 根据entity的包和结构名,生成类型ID
22 | func GenerateTypeID[T any](entity T) (uid string) {
23 | typesOfT := reflect.TypeOf(entity)
24 | return fmt.Sprintf("%s@%s", typesOfT.PkgPath(), typesOfT.Name())
25 | }
26 |
27 | // GeneratePackageID 根据给定的包名,生成其Hash,不保证不会冲突
28 | func GeneratePackageID[ID UintLike](packageName string) (id ID) {
29 | return GenerateHashWithOpts[string, ID](packageName)
30 | }
31 |
32 | // GenerateHash 将任意结构进行哈希,使用SDBM算法作为实现
33 | func GenerateHash[Key any](key Key) (hash uint) {
34 | return GenerateHashWithOpts[Key, uint](key)
35 | }
36 |
37 | // GeneratePrefixHash 将任意结构的前offset字节的内容进行哈希,越界部份不会被计算,使用SDBM算法作为实现
38 | func GeneratePrefixHash[Key any](key Key, offset uintptr) (hash uint) {
39 | return GeneratePrefixHashWithOpts[Key, uint](key, offset)
40 | }
41 |
42 | // GenerateHashWithOpts 将任意结构进行哈希,生成一个指定类型(无符号整形)的哈希值,使用SDBM算法作为实现
43 | func GenerateHashWithOpts[Key any, Hash UintLike](key Key) (hash Hash) {
44 | entityPtr := &key
45 | sum := uint64(0)
46 | start := uintptr(unsafe.Pointer(entityPtr))
47 | end := unsafe.Sizeof(key) + start
48 | offset := unsafe.Sizeof(byte(0))
49 |
50 | for i := start; i < end; i += offset {
51 | byteData := *(*byte)(unsafe.Pointer(i))
52 | sum = uint64(byteData) + (sum << 6) + (sum << 16) - sum
53 | }
54 |
55 | return Hash(sum)
56 | }
57 |
58 | // GeneratePrefixHashWithOpts 将任意结构的前offset字节的内容进行哈希,生成一个指定类型(无符号整形)的哈希值,越界部份不会被计算,使用SDBM算法作为实现
59 | func GeneratePrefixHashWithOpts[Key any, Hash UintLike](key Key, offset uintptr) (hash Hash) {
60 | entityPtr := &key
61 | sum := uint64(0)
62 | start := uintptr(unsafe.Pointer(entityPtr))
63 | end := start + offset
64 | if end > unsafe.Sizeof(key)+start {
65 | end = unsafe.Sizeof(key) + start
66 | }
67 |
68 | byteOffset := unsafe.Sizeof(byte(0))
69 | for i := start; i < end; i += byteOffset {
70 | b := *(*byte)(unsafe.Pointer(i))
71 | sum = uint64(b) + (sum << 6) + (sum << 16) - sum
72 | }
73 |
74 | return Hash(sum)
75 | }
76 |
77 | // GenerateMD5 将给定的字符串使用MD5摘要算法生成摘要
78 | func GenerateMD5(source string) (md5CheckSum string) {
79 | return fmt.Sprintf("%x", md5.Sum([]byte(source)))
80 | }
81 |
82 | // GenerateUID 根据时间戳生成一个UID,毫秒级,48位,其中前6位为设备序列号,后42位为毫秒时间戳,可使用138年
83 | func GenerateUID(mac uint64, timeStamp time.Time) (uid uint64) {
84 | timeStampID := uint64(timeStamp.Sub(GetZeroTimeStamp()).Milliseconds())
85 | timePartBits := uint64(1<<42) - 1
86 | deviceID := GenerateHashWithOpts[uint64, uint8](mac)
87 | devicePart := uint64(1<<6) - 1
88 | return ((uint64(deviceID) & devicePart) << 42) + (timeStampID & timePartBits)
89 | }
90 |
91 | // GenerateRealID 根据MOD的ID和MOD中的ID,拼接出真实ID
92 | func GenerateRealID(uid uint64, sub uint16) (realID uint64) {
93 | uidPartBits := uint64(1<<48) - 1
94 | subPartBits := uint64(1<<16) - 1
95 | return ((uid & uidPartBits) << 16) + (uint64(sub) & subPartBits)
96 | }
97 |
--------------------------------------------------------------------------------
/mod/implement/rule.go:
--------------------------------------------------------------------------------
1 | package implement
2 |
3 | import (
4 | "github.com/sunist-c/genius-invokation-simulator-backend/entity/model"
5 | "github.com/sunist-c/genius-invokation-simulator-backend/enum"
6 | "github.com/sunist-c/genius-invokation-simulator-backend/mod/definition"
7 | )
8 |
9 | var necessaryImplements = []enum.RuleType{
10 | enum.RuleTypeReactionCalculator,
11 | enum.RuleTypeVictorCalculator,
12 | }
13 |
14 | func RuleConvert[dest any](source interface{}) (success bool, result dest) {
15 | converted, ok := source.(dest)
16 | return ok, converted
17 | }
18 |
19 | type RuleImpl struct {
20 | EntityImpl
21 | ruleImpl map[enum.RuleType]interface{}
22 | }
23 |
24 | func (r *RuleImpl) CopyFrom(source definition.Rule, filter ...enum.RuleType) {
25 | if r.ruleImpl == nil {
26 | r.ruleImpl = map[enum.RuleType]interface{}{}
27 | }
28 |
29 | for _, ruleType := range filter {
30 | r.ruleImpl[ruleType] = source.Implements(ruleType)
31 | }
32 | }
33 |
34 | func (r *RuleImpl) Implements(ruleType enum.RuleType) interface{} {
35 | if r.ruleImpl == nil {
36 | r.ruleImpl = map[enum.RuleType]interface{}{}
37 | }
38 |
39 | return r.ruleImpl[ruleType]
40 | }
41 |
42 | func (r *RuleImpl) CheckImplements() (success bool) {
43 | if r.ruleImpl == nil {
44 | return false
45 | }
46 |
47 | if reactionCalculator := r.Implements(enum.RuleTypeReactionCalculator); !debugFlag && reactionCalculator == nil {
48 | return false
49 | } else if success, _ := RuleConvert[model.ReactionCalculator](reactionCalculator); !debugFlag && !success {
50 | return false
51 | }
52 |
53 | if victorCalculator := r.Implements(enum.RuleTypeVictorCalculator); !debugFlag && victorCalculator == nil {
54 | return false
55 | } else if success, _ := RuleConvert[model.VictorCalculator](victorCalculator); !debugFlag && !success {
56 | return false
57 | }
58 |
59 | return true
60 | }
61 |
62 | type RuleImplOptions func(option *RuleImpl)
63 |
64 | func WithRuleID(id uint16) RuleImplOptions {
65 | return func(option *RuleImpl) {
66 | option.EntityImpl.InjectTypeID(uint64(id))
67 | }
68 | }
69 |
70 | func WithRuleImplement(ruleType enum.RuleType, implement interface{}) RuleImplOptions {
71 | return func(option *RuleImpl) {
72 | if option.ruleImpl == nil {
73 | option.ruleImpl = map[enum.RuleType]interface{}{}
74 | }
75 |
76 | option.ruleImpl[ruleType] = implement
77 | }
78 | }
79 |
80 | func WithRuleCopyFrom(another definition.Rule, filter ...enum.RuleType) RuleImplOptions {
81 | return func(option *RuleImpl) {
82 | if option.ruleImpl == nil {
83 | option.ruleImpl = map[enum.RuleType]interface{}{}
84 | }
85 |
86 | for _, ruleType := range filter {
87 | option.ruleImpl[ruleType] = another.Implements(ruleType)
88 | }
89 | }
90 | }
91 |
92 | func NewRuleWithOpts(options ...RuleImplOptions) definition.Rule {
93 | impl := &RuleImpl{
94 | ruleImpl: map[enum.RuleType]interface{}{},
95 | }
96 |
97 | for _, option := range options {
98 | option(impl)
99 | }
100 |
101 | if !impl.CheckImplements() && !debugFlag {
102 | // 未完全实现规则集合,避免在运行时抛出panic,在初始化时抛出
103 | panic("implement check failed")
104 | }
105 |
106 | return impl
107 | }
108 |
--------------------------------------------------------------------------------
/entity/benchmark_test.go:
--------------------------------------------------------------------------------
1 | package entity
2 |
3 | import (
4 | "github.com/sunist-c/genius-invokation-simulator-backend/entity/model"
5 | "github.com/sunist-c/genius-invokation-simulator-backend/enum"
6 | "github.com/sunist-c/genius-invokation-simulator-backend/model/context"
7 | "testing"
8 | )
9 |
10 | func BenchmarkTestCostEquals(b *testing.B) {
11 | tt := struct {
12 | name string
13 | origin map[enum.ElementType]uint
14 | cost map[enum.ElementType]uint
15 | want bool
16 | }{
17 | name: "ElementSetEquals-Mixed-4",
18 | origin: map[enum.ElementType]uint{enum.ElementCryo: 2, enum.ElementDendro: 2, enum.ElementCurrency: 1},
19 | cost: map[enum.ElementType]uint{enum.ElementCurrency: 3, enum.ElementNone: 2},
20 | want: true,
21 | }
22 | originCost := model.NewCostFromMap(tt.origin)
23 | otherCost := model.NewCostFromMap(tt.cost)
24 | b.ResetTimer()
25 | for i := 0; i < b.N; i++ {
26 | if got := originCost.Equals(*otherCost); got != tt.want {
27 | b.Errorf("Error: %v, want %v", got, tt.want)
28 | }
29 | }
30 | }
31 |
32 | func BenchmarkTestPlayerChainNext(b *testing.B) {
33 | pc := newPlayerChain()
34 | for i := uint64(0); i < 100; i++ {
35 | pc.add(i)
36 | }
37 |
38 | b.ResetTimer()
39 | for i := 0; i < b.N; i++ {
40 | pc.next()
41 | }
42 | }
43 |
44 | func BenchmarkTestPlayerChainNextWithComplete(b *testing.B) {
45 | pc := newPlayerChain()
46 | for i := uint64(0); i < 11451419; i++ {
47 | pc.add(i)
48 | }
49 |
50 | b.ResetTimer()
51 | for i := 0; i < b.N; i++ {
52 | exist, _ := pc.next()
53 | pc.complete(uint64(i))
54 | if !exist {
55 | break
56 | }
57 | }
58 | }
59 |
60 | func BenchmarkTestEventMapPreview(b *testing.B) {
61 | m := NewEventMap()
62 | for i := 0; i < 128; i++ {
63 | m.AddEvent(newTestEvent(uint64(i), enum.AfterAttack, true, func(ctx *context.CallbackContext) { ctx.SwitchCharacter(114514) }))
64 | }
65 | m.AddEvent(newTestEvent(uint64(1), enum.AfterSwitch, true, func(ctx *context.CallbackContext) { ctx.ChangeOperated(false) }))
66 | ctx := context.NewCallbackContext()
67 | b.ResetTimer()
68 |
69 | for i := 0; i < b.N; i++ {
70 | m.Preview(enum.AfterSwitch, ctx)
71 | }
72 | }
73 |
74 | func BenchmarkTestEventMapExecute(b *testing.B) {
75 | m := NewEventMap()
76 | for i := 0; i < 128; i++ {
77 | m.AddEvent(newTestEvent(uint64(i), enum.AfterAttack, true, func(ctx *context.CallbackContext) { ctx.SwitchCharacter(114514) }))
78 | }
79 | m.AddEvent(newTestEvent(uint64(1), enum.AfterSwitch, true, func(ctx *context.CallbackContext) { ctx.ChangeOperated(false) }))
80 | ctx := context.NewCallbackContext()
81 | b.ResetTimer()
82 |
83 | for i := 0; i < b.N; i++ {
84 | m.Call(enum.AfterSwitch, ctx)
85 | }
86 | }
87 |
88 | func BenchmarkTestEventMapAdd(b *testing.B) {
89 | m := NewEventMap()
90 | b.ResetTimer()
91 |
92 | for i := 0; i < b.N; i++ {
93 | m.AddEvent(newTestEvent(uint64(1), enum.AfterSwitch, true, func(ctx *context.CallbackContext) { ctx.ChangeOperated(false) }))
94 | }
95 | }
96 |
97 | func BenchmarkTestEventMapRemove(b *testing.B) {
98 | m := NewEventMap()
99 | event := newTestEvent(uint64(1), enum.AfterSwitch, true, func(ctx *context.CallbackContext) { ctx.ChangeOperated(false) })
100 | m.AddEvent(event)
101 | b.ResetTimer()
102 |
103 | for i := 0; i < b.N; i++ {
104 | m.RemoveEvent(event)
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/docs/development/api/localization.md:
--------------------------------------------------------------------------------
1 | 此处为GISB的本地化相关服务,包含语言包的获取、特定词语的翻译等接口
2 |
3 | # 变量与参数
4 |
5 | + `:language_pack_id`/`{language_pack_id}`: 您要获取的语言包的ID,字符串类型,由mod提供者注册
6 |
7 | # 说明
8 |
9 | 本接口使用了硬编码确定语言的ID,其定义如下,从上到下值递增,从0开始:
10 |
11 | ```go
12 | type Language uint
13 |
14 | const (
15 | ChineseSimplified Language = iota // ChineseSimplified 简体中文
16 | ChineseTraditional // ChineseTraditional 繁體中文
17 | English // English
18 | French // French Français
19 | German // German Deutsch
20 | Japanese // Japanese 日本語
21 | Korean // Korean 한국어
22 | Russian // Russian Русский язык
23 | Unknown // Unknown ያንሽቤ
24 | )
25 | ```
26 |
27 | 本文档的所有接口均不需要鉴权
28 |
29 | # 获取语言包
30 |
31 | ## 请求方法与地址
32 |
33 | ```plain text
34 | GET {BaseURL}/localization/language_pack/:language_pack_id
35 | ```
36 |
37 | ## 请求/响应
38 |
39 |
40 |
41 |
42 |
43 | ### 请求
44 |
45 | 本接口不需要提供请求体
46 |
47 | #### 请求示例
48 |
49 | ```http
50 | GET /localization/language_pack/:language_pack_id HTTP/1.1
51 | Host: {BaseURL}
52 | ```
53 |
54 |
55 |
56 | ### 响应
57 |
58 | 此接口将根据您给定的语言包ID,查询并返回语言包
59 |
60 | #### 响应体定义
61 |
62 | ```go
63 | type LocalizationQueryResponse struct {
64 | LanguagePack struct{
65 | Languages map[uint]map[string]string `json:"languages"`
66 | SupportedLanguages []enum.Language `json:"supported_languages"`
67 | } `json:"language_pack"`
68 | }
69 | ```
70 |
71 | #### 响应示例
72 |
73 | ```json
74 | {
75 | "language_pack": {
76 | "languages": {
77 | "0": {
78 | "keqing": "刻晴",
79 | "zhongli": "钟离"
80 | },
81 | "3": {
82 | "keqing": "keqing",
83 | "zhongli": "zhongli"
84 | }
85 | },
86 | "supported_languages": [
87 | 3,
88 | 0
89 | ]
90 | }
91 | }
92 | ```
93 |
94 |
95 |
96 | # 翻译给定词语
97 |
98 | ## 请求方法与地址
99 |
100 | ```plain text
101 | GET {BaseURL}/localization/translate?target_language={target_language}&language_package={language_pack_id}
102 | ```
103 |
104 | ## 查询参数
105 |
106 | + `target_language`: 目标语言
107 | + `language_package`: 词语所在的翻译包ID
108 |
109 | ## 请求/响应
110 |
111 |
112 |
113 |
114 |
115 | ### 请求
116 |
117 | 您需要提供需要查询的词语
118 |
119 | #### 请求体定义
120 |
121 | ```go
122 | type TranslationRequest struct {
123 | Words []string `json:"words"`
124 | }
125 | ```
126 |
127 | #### 请求示例
128 |
129 | ```http
130 | GET /localization/translate?target_language={target_language}&language_package={language_pack_id} HTTP/1.1
131 | Host: {BaseURL}
132 | Content-Type: application/json
133 | Content-Length: 40
134 |
135 | {
136 | "words": ["keqing", "zhongli"]
137 | }
138 | ```
139 |
140 |
141 |
142 | ### 响应
143 |
144 | 此接口将根据您给定的词语与要求的语言包,尝试将其翻译为您指定的语言
145 |
146 | #### 响应体定义
147 |
148 | ```go
149 | type TranslationResponse struct {
150 | Translation map[string]string `json:"translation"`
151 | }
152 | ```
153 |
154 | #### 响应示例
155 |
156 | ```json
157 | {
158 | "translation": {
159 | "keqing": "刻晴",
160 | "zhongli": "钟离"
161 | }
162 | }
163 | ```
164 |
165 |
--------------------------------------------------------------------------------
/mod/implement/skill.go:
--------------------------------------------------------------------------------
1 | package implement
2 |
3 | import (
4 | "github.com/sunist-c/genius-invokation-simulator-backend/enum"
5 | "github.com/sunist-c/genius-invokation-simulator-backend/mod/definition"
6 | )
7 |
8 | type SkillImpl struct {
9 | EntityImpl
10 | skillType enum.SkillType
11 | }
12 |
13 | func (impl *SkillImpl) SkillType() enum.SkillType {
14 | return impl.skillType
15 | }
16 |
17 | type SkillOptions func(option *SkillImpl)
18 |
19 | func WithSkillID(id uint16) SkillOptions {
20 | return func(option *SkillImpl) {
21 | option.InjectTypeID(uint64(id))
22 | }
23 | }
24 |
25 | func WithSkillType(skillType enum.SkillType) SkillOptions {
26 | return func(option *SkillImpl) {
27 | option.skillType = skillType
28 | }
29 | }
30 |
31 | func NewSkillWithOpts(options ...SkillOptions) definition.Skill {
32 | impl := &SkillImpl{}
33 | for _, option := range options {
34 | option(impl)
35 | }
36 |
37 | return impl
38 | }
39 |
40 | type AttackSkillImpl struct {
41 | SkillImpl
42 | skillCost map[enum.ElementType]uint
43 | activeDamage func(ctx definition.Context) definition.Damage
44 | backgroundDamage func(ctx definition.Context) definition.Damage
45 | }
46 |
47 | func (impl *AttackSkillImpl) SkillCost() map[enum.ElementType]uint {
48 | return impl.skillCost
49 | }
50 |
51 | func (impl *AttackSkillImpl) ActiveDamage(ctx definition.Context) definition.Damage {
52 | return impl.activeDamage(ctx)
53 | }
54 |
55 | func (impl *AttackSkillImpl) BackgroundDamage(ctx definition.Context) definition.Damage {
56 | return impl.backgroundDamage(ctx)
57 | }
58 |
59 | type AttackSkillOptions func(option *AttackSkillImpl)
60 |
61 | func WithAttackSkillID(id uint16) AttackSkillOptions {
62 | return func(option *AttackSkillImpl) {
63 | opt := WithSkillID(id)
64 | opt(&option.SkillImpl)
65 | }
66 | }
67 |
68 | func WithAttackSkillType(skillType enum.SkillType) AttackSkillOptions {
69 | return func(option *AttackSkillImpl) {
70 | opt := WithSkillType(skillType)
71 | opt(&option.SkillImpl)
72 | }
73 | }
74 |
75 | func WithAttackSkillCost(skillCost map[enum.ElementType]uint) AttackSkillOptions {
76 | return func(option *AttackSkillImpl) {
77 | option.skillCost = skillCost
78 | }
79 | }
80 |
81 | func WithAttackSkillActiveDamageHandler(handler func(ctx definition.Context) (elementType enum.ElementType, damageAmount uint)) AttackSkillOptions {
82 | return func(option *AttackSkillImpl) {
83 | option.activeDamage = func(ctx definition.Context) definition.Damage {
84 | elementType, damageAmount := handler(ctx)
85 | return NewDamageWithOpts(
86 | WithDamageElementType(elementType),
87 | WithDamageAmount(damageAmount),
88 | )
89 | }
90 | }
91 | }
92 |
93 | func WithAttackSkillBackgroundDamageHandler(handler func(ctx definition.Context) (damageAmount uint)) AttackSkillOptions {
94 | return func(option *AttackSkillImpl) {
95 | option.backgroundDamage = func(ctx definition.Context) definition.Damage {
96 | damageAmount := handler(ctx)
97 | return NewDamageWithOpts(
98 | WithDamageElementType(enum.ElementNone),
99 | WithDamageAmount(damageAmount),
100 | )
101 | }
102 | }
103 | }
104 |
105 | func NewAttackSkillWithOpts(options ...AttackSkillOptions) definition.AttackSkill {
106 | impl := &AttackSkillImpl{}
107 | for _, option := range options {
108 | option(impl)
109 | }
110 |
111 | return impl
112 | }
113 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "github.com/sunist-c/genius-invokation-simulator-backend/exec/backend"
7 | "github.com/sunist-c/genius-invokation-simulator-backend/exec/cli"
8 | "github.com/sunist-c/genius-invokation-simulator-backend/persistence"
9 | "os"
10 | "os/signal"
11 | "path"
12 | "syscall"
13 | "time"
14 | )
15 |
16 | const (
17 | aiMode = "ai"
18 | cliMode = "cli"
19 | backendMode = "backend"
20 | )
21 |
22 | var (
23 | args = &argument{
24 | mode: new(string),
25 | port: new(uint),
26 | conf: new(string),
27 | save: new(bool),
28 | flush: new(uint),
29 | storage: new(string),
30 | }
31 | sig = make(chan os.Signal, 4)
32 | )
33 |
34 | type argument struct {
35 | mode *string
36 | conf *string
37 | port *uint
38 | save *bool
39 | flush *uint
40 | storage *string
41 | }
42 |
43 | func initArgs() {
44 | flag.StringVar(args.conf, "conf", "", "setup the backend configuration file, highest priority")
45 | flag.StringVar(args.mode, "mode", "backend", "setup the startup mode, available [backend, cli, ai]")
46 | flag.UintVar(args.port, "port", 8086, "setup the http protocol port")
47 | flag.BoolVar(args.save, "save", true, "setup if to enable the persistence module")
48 | flag.StringVar(args.storage, "storage", path.Join(os.Args[0], "../data/persistence"), "setup the persistence storage filepath")
49 | flag.UintVar(args.flush, "flush", 3600, "setup the flush frequency(second) of persistence module")
50 | }
51 |
52 | func callQuit() {
53 | fmt.Printf("[main.log] main.callQuit(): daemon running\n")
54 | s := <-sig
55 | fmt.Printf("[main.log] main.callQuit(): quit signal received %v\n", s.String())
56 | fmt.Printf("[main.log] main.callQuit(): quiting persistence\n")
57 | persistence.Quit()
58 | fmt.Printf("[main.log] main.callQuit(): quited persistence\n")
59 |
60 | switch *args.mode {
61 | case backendMode:
62 | backend.Quit()
63 | case cliMode:
64 | cli.Quit()
65 | case aiMode:
66 | panic("not implemented yet")
67 | }
68 |
69 | fmt.Printf("[main.log] main.callQuit(): wait 10 seconds for quit task\n")
70 | time.Sleep(time.Second * 1)
71 | os.Exit(114)
72 | }
73 |
74 | func init() {
75 | errChan := make(chan error, 10)
76 | fmt.Printf("[main.log] main.init(): initializing main package\n")
77 | signal.Notify(sig, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGHUP)
78 | fmt.Printf("[main.log] main.init(): setuped signal channel\n")
79 | initArgs()
80 | flag.Parse()
81 | fmt.Printf("[main.log] main.init(): parsed command line arguments\n")
82 | if err := persistence.SetStoragePath(*args.storage); err != nil {
83 | errChan <- err
84 | panic(err)
85 | }
86 | fmt.Printf("[main.log] main.init(): initialized storage path\n")
87 | persistence.Load(errChan)
88 | fmt.Printf("[main.log] main.init(): initializing persistent storage\n")
89 | go func() {
90 | fmt.Printf("[main.log] main.errorHandler(): error handler running\n")
91 | for err := range errChan {
92 | fmt.Println(err)
93 | }
94 | }()
95 | persistence.Serve(time.Second*time.Duration(*args.flush), errChan)
96 | fmt.Printf("[main.log] main.init(): initialize completed\n")
97 | }
98 |
99 | func main() {
100 | go callQuit()
101 | switch *args.mode {
102 | case backendMode:
103 | backend.Run(*args.port)
104 | case cliMode:
105 | cli.Run()
106 | case aiMode:
107 | panic("not implemented yet")
108 | default:
109 | os.Exit(0)
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/entity/eventmap.go:
--------------------------------------------------------------------------------
1 | package entity
2 |
3 | import (
4 | "github.com/sunist-c/genius-invokation-simulator-backend/entity/model"
5 | "github.com/sunist-c/genius-invokation-simulator-backend/enum"
6 | "github.com/sunist-c/genius-invokation-simulator-backend/model/context"
7 | )
8 |
9 | type Map struct {
10 | sets map[enum.TriggerType]*set
11 | }
12 |
13 | // Call 调用某种类型的Event
14 | func (m *Map) Call(triggerType enum.TriggerType, ctx *context.CallbackContext) {
15 | if set, exist := m.sets[triggerType]; exist {
16 | set.call(ctx)
17 | }
18 | }
19 |
20 | // Preview 预览某种类型的Event
21 | func (m Map) Preview(triggerType enum.TriggerType, ctx *context.CallbackContext) {
22 | if set, exist := m.sets[triggerType]; exist {
23 | set.preview(ctx)
24 | }
25 | }
26 |
27 | // AddEvent 添加一个Event
28 | func (m *Map) AddEvent(event model.Event) {
29 | if set, exist := m.sets[event.TriggerAt()]; !exist || set == nil {
30 | set = newEventSet()
31 | set.add(event)
32 | m.sets[event.TriggerAt()] = set
33 | } else {
34 | m.sets[event.TriggerAt()].add(event)
35 | }
36 | }
37 |
38 | // RemoveEvent 移除一个Event
39 | func (m *Map) RemoveEvent(event model.Event) {
40 | if set, exist := m.sets[event.TriggerAt()]; exist && set != nil {
41 | set.remove(event.TypeID())
42 | }
43 | }
44 |
45 | // AddEvents 添加多个Event,只有类型为filter的Event会生效
46 | func (m *Map) AddEvents(filter enum.TriggerType, events []model.Event) {
47 | if set, exist := m.sets[filter]; !exist || set == nil {
48 | set = newEventSet()
49 | for _, event := range events {
50 | if event.TriggerAt() == filter {
51 | set.add(event)
52 | }
53 | }
54 | m.sets[filter] = set
55 | } else {
56 | for _, event := range events {
57 | if event.TriggerAt() == filter {
58 | set.add(event)
59 | }
60 | }
61 | }
62 | }
63 |
64 | // RemoveEvents 移除类型为filter的Event
65 | func (m *Map) RemoveEvents(filter enum.TriggerType) {
66 | delete(m.sets, filter)
67 | }
68 |
69 | func (m Map) Expose(trigger enum.TriggerType) (events []uint64) {
70 | if set, exist := m.sets[trigger]; !exist {
71 | return []uint64{}
72 | } else {
73 | events = []uint64{}
74 | for id := range set.events {
75 | events = append(events, id)
76 | }
77 | return events
78 | }
79 | }
80 |
81 | func NewEventMap() *Map {
82 | return &Map{sets: map[enum.TriggerType]*set{}}
83 | }
84 |
85 | type set struct {
86 | events map[uint64]model.Event
87 | }
88 |
89 | // call 调用EventSet中的所有Event,并在调用完成后清理需要清理的Event
90 | func (s *set) call(ctx *context.CallbackContext) {
91 | for _, e := range s.events {
92 | if e.CanTriggered(*ctx) {
93 | e.Callback(ctx)
94 | delete(s.events, e.TypeID())
95 | }
96 | }
97 | }
98 |
99 | // preview 调用EventSet中的所有Event,但调用完成后不清理调用过的Event
100 | func (s set) preview(ctx *context.CallbackContext) {
101 | for _, e := range s.events {
102 | if e.CanTriggered(*ctx) {
103 | e.Callback(ctx)
104 | }
105 | }
106 | }
107 |
108 | // append 合并两个EventSet,若新Set中有同id的Event,则会覆盖现有Event
109 | func (s *set) append(another *set) {
110 | for id, event := range another.events {
111 | s.events[id] = event
112 | }
113 | }
114 |
115 | // add 向EventSet中加入一个Event
116 | func (s *set) add(event model.Event) {
117 | s.events[event.TypeID()] = event
118 | }
119 |
120 | // remove 从EventSet中移除一个指定id的Event
121 | func (s *set) remove(id uint64) {
122 | delete(s.events, id)
123 | }
124 |
125 | // newEventSet 创建一个空EventSet
126 | func newEventSet() *set {
127 | return &set{events: map[uint64]model.Event{}}
128 | }
129 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ### VisualStudioCode template
2 | .vscode/*
3 | !.vscode/settings.json
4 | !.vscode/tasks.json
5 | !.vscode/launch.json
6 | !.vscode/extensions.json
7 | *.code-workspace
8 |
9 | # Local History for Visual Studio Code
10 | .history/
11 |
12 | ### JetBrains template
13 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
14 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
15 |
16 | # User-specific stuff
17 | .idea/**/workspace.xml
18 | .idea/**/tasks.xml
19 | .idea/**/usage.statistics.xml
20 | .idea/**/dictionaries
21 | .idea/**/shelf
22 |
23 | # Generated files
24 | .idea/**/contentModel.xml
25 |
26 | # Sensitive or high-churn files
27 | .idea/**/dataSources/
28 | .idea/**/dataSources.ids
29 | .idea/**/dataSources.local.xml
30 | .idea/**/sqlDataSources.xml
31 | .idea/**/dynamic.xml
32 | .idea/**/uiDesigner.xml
33 | .idea/**/dbnavigator.xml
34 |
35 | # Gradle
36 | .idea/**/gradle.xml
37 | .idea/**/libraries
38 |
39 | # Gradle and Maven with auto-import
40 | # When using Gradle or Maven with auto-import, you should exclude module files,
41 | # since they will be recreated, and may cause churn. Uncomment if using
42 | # auto-import.
43 | # .idea/artifacts
44 | # .idea/compiler.xml
45 | # .idea/jarRepositories.xml
46 | # .idea/modules.xml
47 | # .idea/*.iml
48 | # .idea/modules
49 | # *.iml
50 | # *.ipr
51 |
52 | # CMake
53 | cmake-build-*/
54 |
55 | # Mongo Explorer plugin
56 | .idea/**/mongoSettings.xml
57 |
58 | # File-based project format
59 | *.iws
60 |
61 | # IntelliJ
62 | out/
63 |
64 | # mpeltonen/sbt-idea plugin
65 | .idea_modules/
66 |
67 | # JIRA plugin
68 | atlassian-ide-plugin.xml
69 |
70 | # Cursive Clojure plugin
71 | .idea/replstate.xml
72 |
73 | # Crashlytics plugin (for Android Studio and IntelliJ)
74 | com_crashlytics_export_strings.xml
75 | crashlytics.properties
76 | crashlytics-build.properties
77 | fabric.properties
78 |
79 | # Editor-based Rest Client
80 | .idea/httpRequests
81 |
82 | # Android studio 3.1+ serialized cache file
83 | .idea/caches/build_file_checksums.ser
84 |
85 | ### Go template
86 | # Binaries for programs and plugins
87 | *.exe
88 | *.exe~
89 | *.dll
90 | *.so
91 | *.dylib
92 |
93 | # Test binary, built with `go test -c`
94 | *.test
95 |
96 | # Output of the go coverage tool, specifically when used with LiteIDE
97 | *.out
98 |
99 | # Dependency directories (remove the comment below to include it)
100 | # vendor/
101 |
102 | ### Windows template
103 | # Windows thumbnail cache files
104 | Thumbs.db
105 | Thumbs.db:encryptable
106 | ehthumbs.db
107 | ehthumbs_vista.db
108 |
109 | # Dump file
110 | *.stackdump
111 |
112 | # Folder config file
113 | [Dd]esktop.ini
114 |
115 | # Recycle Bin used on file shares
116 | $RECYCLE.BIN/
117 |
118 | # Windows Installer files
119 | *.cab
120 | *.msi
121 | *.msix
122 | *.msm
123 | *.msp
124 |
125 | # Windows shortcuts
126 | *.lnk
127 |
128 | ### macOS template
129 | # General
130 | .DS_Store
131 | .AppleDouble
132 | .LSOverride
133 |
134 | # Icon must end with two \r
135 | Icon
136 |
137 | # Thumbnails
138 | ._*
139 |
140 | # Files that might appear in the root of a volume
141 | .DocumentRevisions-V100
142 | .fseventsd
143 | .Spotlight-V100
144 | .TemporaryItems
145 | .Trashes
146 | .VolumeIcon.icns
147 | .com.apple.timemachine.donotpresent
148 |
149 | # Directories potentially created on remote AFP share
150 | .AppleDB
151 | .AppleDesktop
152 | Network Trash Folder
153 | Temporary Items
154 | .apdisk
155 |
156 | # Full idea configuration
157 | .idea
158 |
159 | # go.sum
160 | go.sum
161 |
162 | # metadata
163 | /**/metadata.yml
--------------------------------------------------------------------------------
/protocol/websocket/engine.go:
--------------------------------------------------------------------------------
1 | package websocket
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "github.com/gorilla/websocket"
6 | "github.com/sunist-c/genius-invokation-simulator-backend/protocol/websocket/config"
7 | "net/http"
8 | "time"
9 | )
10 |
11 | func checkOriginHandler() func(r *http.Request) bool {
12 | conf := config.GetConfig()
13 | if conf.AllowCrossOrigin {
14 | return func(r *http.Request) bool {
15 | return true
16 | }
17 | } else {
18 | return func(r *http.Request) bool {
19 | // todo: optimize check origin logic
20 | origin := r.Header.Get("Origin")
21 | for _, allow := range conf.AllowOriginDomains {
22 | if origin == allow {
23 | return true
24 | }
25 | }
26 |
27 | return false
28 | }
29 | }
30 | }
31 |
32 | func newUpgrader() *websocket.Upgrader {
33 | conf := config.GetConfig()
34 | return &websocket.Upgrader{
35 | HandshakeTimeout: time.Second * time.Duration(conf.HandshakeTimeout),
36 | ReadBufferSize: int(conf.WebsocketReaderBufferSize),
37 | WriteBufferSize: int(conf.WebsocketWriterBufferSize),
38 | CheckOrigin: checkOriginHandler(),
39 | }
40 | }
41 |
42 | type Connection struct {
43 | conn *websocket.Conn
44 | exitChan chan struct{}
45 | errChan chan error
46 | iStream chan []byte // iStream 输入管道
47 | oStream chan []byte // oStream 输出管道
48 | }
49 |
50 | func (c *Connection) readLoop() {
51 | for {
52 | select {
53 | case <-c.exitChan:
54 | // 向exitChan写入信息通知其他协程
55 | c.exitChan <- struct{}{}
56 | return
57 | default:
58 | if messageType, reader, err := c.conn.ReadMessage(); err != nil {
59 | // 发生错误,关闭WebSocket连接
60 | c.errChan <- err
61 | c.Close()
62 | } else if messageType == websocket.TextMessage {
63 | // 传输格式为json,只监听TextMessage
64 | c.iStream <- reader
65 | }
66 | }
67 | }
68 | }
69 |
70 | func (c *Connection) writeLoop() {
71 | for {
72 | select {
73 | case <-c.exitChan:
74 | // 向exitChan写入信息通知其他协程
75 | c.exitChan <- struct{}{}
76 | return
77 | case outMessage := <-c.oStream:
78 | if err := c.conn.WriteMessage(websocket.TextMessage, outMessage); err != nil {
79 | // 发生错误,关闭WebSocket连接
80 | c.errChan <- err
81 | c.Close()
82 | }
83 | }
84 | }
85 | }
86 |
87 | // Serve 启动WebsocketConnection的服务协程
88 | func (c *Connection) Serve() {
89 | go c.readLoop()
90 | go c.writeLoop()
91 | }
92 |
93 | func (c *Connection) Close() {
94 | // 关闭客户端连接
95 | if err := c.conn.Close(); err != nil {
96 | c.errChan <- err
97 | }
98 |
99 | // 通知readLoop和writeLoop退出
100 | c.exitChan <- struct{}{}
101 |
102 | // 释放WebSocketConnection
103 | c.conn = nil
104 | close(c.iStream)
105 | close(c.oStream)
106 | close(c.exitChan)
107 | }
108 |
109 | func (c *Connection) Write(jsonBytes []byte) {
110 | c.oStream <- jsonBytes
111 | }
112 |
113 | func (c *Connection) Read() <-chan []byte {
114 | return c.iStream
115 | }
116 |
117 | func NewConnection(conn *websocket.Conn, errChan chan error) *Connection {
118 | conf := config.GetConfig()
119 | return &Connection{
120 | conn: conn,
121 | iStream: make(chan []byte, conf.ServerMessageBufferSize),
122 | oStream: make(chan []byte, conf.ServerMessageBufferSize),
123 | exitChan: make(chan struct{}, 2),
124 | errChan: errChan,
125 | }
126 | }
127 |
128 | // Upgrade 将HTTP连接升级为Websocket连接
129 | func Upgrade(ctx *gin.Context, errChan chan error) (success bool, connection *Connection) {
130 | if conn, err := newUpgrader().Upgrade(ctx.Writer, ctx.Request, ctx.Request.Header); err != nil {
131 | return false, nil
132 | } else {
133 | return true, NewConnection(conn, errChan)
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # 1. 综述
2 |
3 | ## 1.1 概况
4 |
5 | 这里是原神(Genshin Impact)的《七圣召唤》模拟器,是参考原神3.3版本的「七圣召唤」玩法重新实现的后端(服务端),包括所有的原神内的游戏内容,并拓展一些米哈游没有做的内容。
6 |
7 | ## 1.2 声明
8 |
9 | 本项目的交流群(QQ)为`530924402`,欢迎讨论与PR
10 |
11 | 本模拟器侧重于提供自定义的七圣召唤对局,比如每次投十二个骰子/每回合摸四张牌/加入自定义角色、卡牌等功能,~~短期内没有~~ 项目稳定后尽快针对ai训练进行优化、适配。
12 | 考虑设计接口时兼容RLcard
13 |
14 | 相关的[genius-invokation-gym](https://github.com/paladin1013/genius-invokation-gym)项目侧重于提供ai相关接口,请根据需求选择
15 |
16 | **本模拟器接口尽量和[genius-invokation-gym](https://github.com/paladin1013/genius-invokation-gym)保持一致,其项目完善后本项目也尽量拓展相应的ai接口**
17 |
18 | 同时感谢[@Leng Yue](https://github.com/leng-yue)实现的前端项目[genius-invokation-webui](https://github.com/leng-yue/genius-invokation-webui)
19 |
20 | > **本项目不含任何原神(Genshin Impact)的美术素材,原神(Genshin Impact)的相关版权归米哈游(miHoYo)所有**
21 |
22 | ## 1.3 技术栈与组件
23 |
24 | + golang 1.19: 这是本项目的编码语言与运行环境
25 | + mingw-gcc/clang/gcc: 因为内嵌sqlite3产生的cgo编译需求
26 | + sqlite3: 这是本项目用于存储玩家信息、玩家卡组信息的持久化组件,已内嵌,无需下载
27 |
28 | # 2. 徽章
29 |
30 | ## 2.1 构建情况
31 |
32 | | Branch | master | dev | release |
33 | | :--: | :--: | :--: | :--: |
34 | | drone-ci | [](https://drone.sunist.cn/sunist-c/genius-invokation-simulator-backend) | [](https://drone.sunist.cn/sunist-c/genius-invokation-simulator-backend) | nil |
35 | | github-action | [](https://github.com/sunist-c/genius-invokation-simulator-backend/actions/workflows/go.yml) | [](https://github.com/sunist-c/genius-invokation-simulator-backend/actions/workflows/go.yml) | nil |
36 |
37 | ## 2.2 代码质量
38 |
39 | [](https://goreportcard.com/report/github.com/sunist-c/genius-invokation-simulator-backend)
40 |
41 | # 3. 分支说明
42 |
43 | - master 主要的分支,将在发生重要功能修改时与dev分支进行同步
44 | - dev 开发中的分支,将频繁地进行更改与新功能测试
45 | - release 稳定的分支,仅会在重大版本更新时进行功能合并
46 |
47 | # 4. 开发进度
48 |
49 | 请转到[gisb's feature development](https://github.com/users/sunist-c/projects/2)查看项目进度
50 |
51 | # 5. 参与项目
52 |
53 | 如果您想增加一个功能或想法:
54 |
55 | 1. 加入本项目的交流群或[genius-invokation-gym](https://github.com/paladin1013/genius-invokation-gym)的交流群或在本项目的[Discussion/Ideas](https://github.com/sunist-c/genius-invokation-simulator-backend/discussions/categories/ideas)中分享您的想法
56 | 2. 在取得Contributor的广泛认可后将为您创建一个WIP的issue
57 | 3. 按照正常的流程fork->coding->pull request您的修改
58 |
59 | 如果您想为项目贡献代码,您可以转到[gisb's feature development](https://github.com/users/sunist-c/projects/2)查看这个项目目前在干什么,标有`help wanted`的内容可能需要一些帮助,处于`design`阶段的内容目前还没有进行开发,您可以直接在GitHub Projects/Project Issue页面与我们交流
60 |
61 | 本项目有意向从Jetbrains申请开源项目的All Products License,将会提供给Code Contributors
62 |
63 | # 6. 功能特性
64 |
65 | - [x] 游戏基本玩法
66 | - [ ] 元素反应
67 | - [x] 角色与技能
68 | - [ ] 圣遗物与武器
69 | - [ ] 场景和伙伴
70 | - [ ] 召唤物
71 | - [x] 卡牌与元素转化
72 | - [ ] 游戏拓展玩法
73 | - [x] 多人游戏(玩家`N>=2`,队伍`N>=2`)
74 | - [ ] 创建对局
75 | - [ ] 匹配对局
76 | - [ ] 游戏内通信
77 | - [ ] 观战模式
78 | - [ ] 比赛模式
79 | - [ ] 作战记录
80 | - [x] 自定义Mod支持
81 | - [x] Go/Lua支持
82 | - [x] 自定义角色与卡牌
83 | - [x] 自定义规则
84 | - [x] 多种通信协议连接
85 | - [x] websocket接口
86 | - [x] http/https接口
87 | - [ ] udp/kcp接口
88 | - [ ] rpc接口
89 | - [ ] 分布式支持
90 | - [ ] 自动化部署
91 | - [ ] 服务注册与服务发现
92 | - [ ] 服务端负载均衡
93 | - [ ] 管理功能
94 | - [ ] 公告与通知
95 | - [ ] IP追踪/封禁
96 | - [ ] QPS/TPS限制器
97 |
--------------------------------------------------------------------------------
/entity/model/cost.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "math/rand"
5 | "time"
6 |
7 | "github.com/sunist-c/genius-invokation-simulator-backend/enum"
8 | )
9 |
10 | var (
11 | random = rand.New(rand.NewSource(time.Now().UnixMilli()))
12 | )
13 |
14 | func init() {
15 | random.Seed(time.Now().UnixNano())
16 | }
17 |
18 | type Cost struct {
19 | costs map[enum.ElementType]uint
20 | total uint
21 | }
22 |
23 | // sub 从Cost中减少amount个element类型的元素骰子
24 | func (c *Cost) sub(element enum.ElementType, amount uint) {
25 | if c.costs[element] >= amount {
26 | c.total -= amount
27 | c.costs[element] -= amount
28 | } else {
29 | c.total -= c.costs[element]
30 | c.costs[element] = 0
31 | }
32 | }
33 |
34 | // add 向Cost中增加amount个element类型的元素骰子
35 | func (c *Cost) add(element enum.ElementType, amount uint) {
36 | c.total += amount
37 | c.costs[element] += amount
38 | }
39 |
40 | // Pay 从Cost中减去other中的元素骰子,不含等价判断
41 | func (c *Cost) Pay(other Cost) {
42 | for element, amount := range other.costs {
43 | c.sub(element, amount)
44 | }
45 | }
46 |
47 | // Add 想Cost中增加other中的所有元素骰子,不含等价判断
48 | func (c *Cost) Add(other Cost) {
49 | for element, amount := range other.costs {
50 | c.add(element, amount)
51 | }
52 | }
53 |
54 | // Contains 判断Cost中是否包含other中的所有元素,含等价判断
55 | func (c Cost) Contains(other Cost) bool {
56 | // 判断费用总数
57 | if other.total > c.total {
58 | return false
59 | }
60 |
61 | // 先减去确定类型的费用
62 | for element := enum.ElementStartIndex; element <= enum.ElementEndIndex; element++ {
63 | if other.costs[element] > c.costs[element]+c.costs[enum.ElementCurrency] {
64 | return false
65 | } else {
66 | if other.costs[element] > c.costs[element] {
67 | c.costs[enum.ElementCurrency] -= other.costs[element] - c.costs[element]
68 | c.costs[element] = 0
69 | } else {
70 | c.costs[element] -= other.costs[element]
71 | }
72 | }
73 | }
74 |
75 | // 判断是否满足同色元素要求
76 | for element := enum.ElementStartIndex; element <= enum.ElementEndIndex; element++ {
77 | if other.costs[enum.ElementSame] <= c.costs[element]+c.costs[enum.ElementCurrency] && element != enum.ElementCurrency {
78 | return true
79 | }
80 | }
81 |
82 | return false
83 | }
84 |
85 | // Equals 判断Cost中的元素是否和other中的元素完全等价
86 | func (c Cost) Equals(other Cost) bool {
87 | return c.Contains(other) && c.total == other.total
88 | }
89 |
90 | func (c Cost) Costs() map[enum.ElementType]uint {
91 | return c.costs
92 | }
93 |
94 | func (c Cost) Total() uint {
95 | return c.total
96 | }
97 |
98 | // NewCost 创建一个空Cost
99 | func NewCost() *Cost {
100 | return &Cost{
101 | costs: map[enum.ElementType]uint{},
102 | total: 0,
103 | }
104 | }
105 |
106 | // NewCostFromMap 从一个map创建Cost
107 | func NewCostFromMap(m map[enum.ElementType]uint) *Cost {
108 | result := &Cost{
109 | costs: map[enum.ElementType]uint{},
110 | total: 0,
111 | }
112 |
113 | for elementType, amount := range m {
114 | result.costs[elementType] = amount
115 | result.total += amount
116 | }
117 |
118 | return result
119 | }
120 |
121 | // NewRandomCost 创建一个随机费用的Cost
122 | func NewRandomCost(elementAmount uint) *Cost {
123 | costMap := map[enum.ElementType]uint{}
124 | for elementAmount > 0 {
125 | if elementAmount <= 21 {
126 | source := random.Uint64()
127 | for i := uint(0); i < elementAmount; i++ {
128 | element := enum.ElementType(source % 8)
129 | costMap[element] += 1
130 | source = source >> 3
131 | }
132 | elementAmount = 0
133 | } else {
134 | source := random.Uint64()
135 | for i := 0; i < 21; i++ {
136 | element := enum.ElementType(source % 8)
137 | costMap[element] += 1
138 | source = source >> 3
139 | }
140 | elementAmount -= 21
141 | }
142 | }
143 | return NewCostFromMap(costMap)
144 | }
145 |
--------------------------------------------------------------------------------
/model/context/callback.go:
--------------------------------------------------------------------------------
1 | package context
2 |
3 | import (
4 | "github.com/sunist-c/genius-invokation-simulator-backend/enum"
5 | "github.com/sunist-c/genius-invokation-simulator-backend/model/kv"
6 | )
7 |
8 | type CallbackContext struct {
9 | changeElements kv.Pair[bool, *CostContext]
10 | changeCharge kv.Pair[bool, *ChargeContext]
11 | changeModifiers kv.Pair[bool, *ModifierContext]
12 | attachElement kv.Pair[bool, map[uint64]enum.ElementType]
13 | getCards kv.Pair[bool, uint]
14 | findCard kv.Pair[bool, enum.CardType]
15 | switchCharacter kv.Pair[bool, uint64]
16 | operated kv.Pair[bool, bool]
17 | }
18 |
19 | func (c *CallbackContext) ChangeElements(f func(ctx *CostContext)) {
20 | if !c.changeElements.Key() {
21 | c.changeElements.SetKey(true)
22 | }
23 | f(c.changeElements.Value())
24 | }
25 |
26 | func (c *CallbackContext) ChangeCharge(f func(ctx *ChargeContext)) {
27 | if !c.changeCharge.Key() {
28 | c.changeCharge.SetKey(true)
29 | }
30 | f(c.changeCharge.Value())
31 | }
32 |
33 | func (c *CallbackContext) ChangeModifiers(f func(ctx *ModifierContext)) {
34 | if !c.changeModifiers.Key() {
35 | c.changeModifiers.SetKey(true)
36 | }
37 | f(c.changeModifiers.Value())
38 | }
39 |
40 | func (c *CallbackContext) AttachElement(target uint64, element enum.ElementType) {
41 | if !c.attachElement.Key() {
42 | c.attachElement.SetKey(true)
43 | }
44 | value := c.attachElement.Value()
45 | value[target] = element
46 | c.attachElement.SetValue(value)
47 | }
48 |
49 | func (c *CallbackContext) GetCards(amount uint) {
50 | if !c.getCards.Key() {
51 | c.getCards.SetKey(true)
52 | }
53 | c.getCards.SetValue(amount)
54 | }
55 |
56 | func (c *CallbackContext) FindCard(cardType enum.CardType) {
57 | if !c.findCard.Key() {
58 | c.findCard.SetKey(true)
59 | }
60 | c.findCard.SetValue(cardType)
61 | }
62 |
63 | func (c *CallbackContext) SwitchCharacter(target uint64) {
64 | if !c.switchCharacter.Key() {
65 | c.switchCharacter.SetKey(true)
66 | }
67 | c.switchCharacter.SetValue(target)
68 | }
69 |
70 | func (c *CallbackContext) ChangeOperated(operated bool) {
71 | if !c.operated.Key() {
72 | c.operated.SetKey(true)
73 | }
74 | c.operated.SetValue(operated)
75 | }
76 |
77 | func (c CallbackContext) ChangeElementsResult() (changed bool, result *CostContext) {
78 | return c.changeElements.Key(), c.changeElements.Value()
79 | }
80 |
81 | func (c CallbackContext) ChangeChargeResult() (changed bool, result *ChargeContext) {
82 | return c.changeCharge.Key(), c.changeCharge.Value()
83 | }
84 |
85 | func (c CallbackContext) ChangeModifiersResult() (changed bool, result *ModifierContext) {
86 | return c.changeModifiers.Key(), c.changeModifiers.Value()
87 | }
88 |
89 | func (c CallbackContext) AttachElementResult() (changed bool, result map[uint64]enum.ElementType) {
90 | return c.attachElement.Key(), c.attachElement.Value()
91 | }
92 |
93 | func (c CallbackContext) GetCardsResult() (changed bool, result uint) {
94 | return c.getCards.Key(), c.getCards.Value()
95 | }
96 |
97 | func (c CallbackContext) GetFindCardResult() (find bool, cardType enum.CardType) {
98 | return c.findCard.Key(), c.findCard.Value()
99 | }
100 |
101 | func (c CallbackContext) SwitchCharacterResult() (switched bool, target uint64) {
102 | return c.switchCharacter.Key(), c.switchCharacter.Value()
103 | }
104 |
105 | func (c CallbackContext) ChangeOperatedResult() (switched, operated bool) {
106 | return c.operated.Key(), c.operated.Value()
107 | }
108 |
109 | func NewCallbackContext() *CallbackContext {
110 | return &CallbackContext{
111 | changeElements: kv.NewPair(false, NewCostContext()),
112 | changeCharge: kv.NewPair(false, NewChargeContext()),
113 | changeModifiers: kv.NewPair(false, NewModifierContext()),
114 | attachElement: kv.NewPair(false, map[uint64]enum.ElementType{}),
115 | getCards: kv.NewPair(false, uint(0)),
116 | switchCharacter: kv.NewPair(false, uint64(0)),
117 | operated: kv.NewPair(false, false),
118 | findCard: kv.NewPair(false, enum.CardType(0)),
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/model/context/damage.go:
--------------------------------------------------------------------------------
1 | package context
2 |
3 | import (
4 | "github.com/sunist-c/genius-invokation-simulator-backend/enum"
5 | )
6 |
7 | type Damage struct {
8 | elementType enum.ElementType
9 | reaction enum.Reaction
10 | amount uint
11 | }
12 |
13 | func (d *Damage) add(amount uint) {
14 | d.amount += amount
15 | }
16 |
17 | func (d *Damage) sub(amount uint) {
18 | if d.amount > amount {
19 | d.amount -= amount
20 | } else {
21 | d.amount = 0
22 | }
23 | }
24 |
25 | func (d *Damage) change(element enum.ElementType) {
26 | d.elementType = element
27 | }
28 |
29 | // Amount 伤害的数值,只读
30 | func (d Damage) Amount() uint {
31 | return d.amount
32 | }
33 |
34 | // ElementType 伤害的元素类型,只读
35 | func (d Damage) ElementType() enum.ElementType {
36 | return d.elementType
37 | }
38 |
39 | // Reaction 伤害发生的反应,只读
40 | func (d Damage) Reaction() enum.Reaction {
41 | return d.reaction
42 | }
43 |
44 | type DamageContext struct {
45 | skillID uint64
46 | sendCharacter uint64
47 | targetCharacter uint64
48 | backgroundCharacters []uint64
49 | damages map[uint64]*Damage
50 | }
51 |
52 | // AddActiveDamage 增加对目标玩家前台角色的伤害数值
53 | func (d *DamageContext) AddActiveDamage(amount uint) {
54 | d.damages[d.targetCharacter].add(amount)
55 | }
56 |
57 | // AddPenetratedDamage 增加对目标玩家所有后台角色的穿透伤害数值
58 | func (d *DamageContext) AddPenetratedDamage(amount uint) {
59 | for _, character := range d.backgroundCharacters {
60 | d.damages[character].add(amount)
61 | }
62 | }
63 |
64 | // SubActiveDamage 降低对目标玩家前台角色的伤害数值
65 | func (d *DamageContext) SubActiveDamage(amount uint) {
66 | d.damages[d.targetCharacter].sub(amount)
67 | }
68 |
69 | // SubPenetratedDamage 降低对目标玩家所有后台角色的穿透伤害数值
70 | func (d *DamageContext) SubPenetratedDamage(amount uint) {
71 | for _, character := range d.backgroundCharacters {
72 | d.damages[character].sub(amount)
73 | }
74 | }
75 |
76 | // ChangeElementType 修改对目标玩家前台角色的伤害元素类型
77 | func (d *DamageContext) ChangeElementType(element enum.ElementType) {
78 | d.damages[d.targetCharacter].change(element)
79 | }
80 |
81 | // SetReaction 目标角色身上发生的元素反应类型,仅供框架使用
82 | func (d *DamageContext) SetReaction(targetCharacter uint64, reaction enum.Reaction) {
83 | d.damages[targetCharacter].reaction = reaction
84 | }
85 |
86 | // GetTargetCharacter 获取伤害的目标角色ID
87 | func (d DamageContext) GetTargetCharacter() uint64 {
88 | return d.targetCharacter
89 | }
90 |
91 | // GetBackgroundCharacters 获取伤害的目标后台角色ID
92 | func (d DamageContext) GetBackgroundCharacters() []uint64 {
93 | return d.backgroundCharacters
94 | }
95 |
96 | // GetTargetCharacterReaction 获取伤害的目标角色发生的元素反应
97 | func (d DamageContext) GetTargetCharacterReaction() enum.Reaction {
98 | return d.damages[d.targetCharacter].reaction
99 | }
100 |
101 | // Damage 返回DamageContext携带的伤害信息,只读
102 | func (d DamageContext) Damage() map[uint64]Damage {
103 | result := map[uint64]Damage{}
104 | for _, id := range d.backgroundCharacters {
105 | result[id] = Damage{elementType: enum.ElementNone, amount: 0}
106 | }
107 |
108 | for target, damage := range d.damages {
109 | result[target] = *damage
110 | }
111 |
112 | return result
113 | }
114 |
115 | // NewEmptyDamageContext 新建一个空的DamageContext
116 | func NewEmptyDamageContext(skill, from, target uint64, backgrounds []uint64) *DamageContext {
117 | return &DamageContext{
118 | skillID: skill,
119 | sendCharacter: from,
120 | targetCharacter: target,
121 | backgroundCharacters: backgrounds,
122 | damages: map[uint64]*Damage{},
123 | }
124 | }
125 |
126 | // NewDamageContext 新建一个带有基础伤害的DamageContext
127 | func NewDamageContext(skill, from, target uint64, backgrounds []uint64, elementType enum.ElementType, damageAmount uint) *DamageContext {
128 | return &DamageContext{
129 | skillID: skill,
130 | sendCharacter: from,
131 | targetCharacter: target,
132 | backgroundCharacters: backgrounds,
133 | damages: map[uint64]*Damage{target: {elementType: elementType, amount: damageAmount, reaction: enum.ReactionNone}},
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/entity/character.go:
--------------------------------------------------------------------------------
1 | package entity
2 |
3 | import (
4 | "github.com/sunist-c/genius-invokation-simulator-backend/entity/model"
5 | "github.com/sunist-c/genius-invokation-simulator-backend/enum"
6 | "github.com/sunist-c/genius-invokation-simulator-backend/model/modifier/definition"
7 | )
8 |
9 | type character struct {
10 | id uint64 // id 角色的ID,由框架确定
11 | player uint64 // player 所属玩家的ID,由框架确定
12 | affiliation enum.Affiliation // affiliation 角色的势力归属
13 | vision enum.ElementType // vision 角色的元素类型
14 | weapon enum.WeaponType // weapon 角色的武器类型
15 | skills map[uint64]model.Skill // skills 角色的技能
16 |
17 | maxHP uint // maxHP 角色的最大生命值
18 | currentHP uint // currentHP 角色的当前生命值
19 | maxMP uint // maxMP 角色的最大能量值
20 | currentMP uint // currentMP 角色的当前能量值
21 | status enum.CharacterStatus // status 角色的状态
22 | elements []enum.ElementType // elements 角色目前附着的元素
23 | satiety bool // satiety 角色的饱腹状态
24 | equipments map[enum.EquipmentType]uint64 // equipments 角色穿着的装备
25 |
26 | localDirectAttackModifiers definition.AttackModifiers // localDirectAttackModifiers 本地直接攻击修正
27 | localFinalAttackModifiers definition.AttackModifiers // localFinalAttackModifiers 本地最终攻击修正
28 | localDefenceModifiers definition.DefenceModifiers // localDefenceModifiers 本地防御修正
29 | localChargeModifiers definition.ChargeModifiers // localChargeModifiers 本地充能修正
30 | localHealModifiers definition.HealModifiers // localHealModifiers 本地治疗修正
31 | localCostModifiers definition.CostModifiers // localCostModifiers 本地费用修正
32 | }
33 |
34 | func (c character) GetID() (id uint64) {
35 | return c.id
36 | }
37 |
38 | func (c character) GetOwner() (owner uint64) {
39 | return c.player
40 | }
41 |
42 | func (c character) GetAffiliation() (affiliation enum.Affiliation) {
43 | return c.affiliation
44 | }
45 |
46 | func (c character) GetVision() (element enum.ElementType) {
47 | return c.vision
48 | }
49 |
50 | func (c character) GetWeaponType() (weaponType enum.WeaponType) {
51 | return c.weapon
52 | }
53 |
54 | func (c character) GetSkills() (skills []uint64) {
55 | skills = make([]uint64, 0)
56 | for id := range c.skills {
57 | skills = append(skills, id)
58 | }
59 | return skills
60 | }
61 |
62 | func (c character) GetHP() (hp uint) {
63 | return c.currentHP
64 | }
65 |
66 | func (c character) GetMaxHP() (maxHP uint) {
67 | return c.maxHP
68 | }
69 |
70 | func (c character) GetMP() (mp uint) {
71 | return c.currentMP
72 | }
73 |
74 | func (c character) GetMaxMP() (maxMP uint) {
75 | return c.maxMP
76 | }
77 |
78 | func (c character) GetEquipment(equipmentType enum.EquipmentType) (equipped bool, equipment uint64) {
79 | equipmentID, exist := c.equipments[equipmentType]
80 | return exist, equipmentID
81 | }
82 |
83 | func (c character) GetSatiety() (satiety bool) {
84 | return c.satiety
85 | }
86 |
87 | func (c character) GetAttachedElements() (elements []enum.ElementType) {
88 | return c.elements
89 | }
90 |
91 | func (c character) GetStatus() (status enum.CharacterStatus) {
92 | return c.status
93 | }
94 |
95 | func (c character) GetLocalModifiers(modifierType enum.ModifierType) (modifiers []uint64) {
96 | switch modifierType {
97 | case enum.ModifierTypeNone:
98 | return []uint64{}
99 | case enum.ModifierTypeAttack:
100 | modifiers = []uint64{}
101 | modifiers = append(modifiers, c.localDirectAttackModifiers.Expose()...)
102 | modifiers = append(modifiers, c.localFinalAttackModifiers.Expose()...)
103 | return modifiers
104 | case enum.ModifierTypeCharacter:
105 | return []uint64{}
106 | case enum.ModifierTypeCharge:
107 | return c.localChargeModifiers.Expose()
108 | case enum.ModifierTypeCost:
109 | return c.localCostModifiers.Expose()
110 | case enum.ModifierTypeDefence:
111 | return c.localDefenceModifiers.Expose()
112 | case enum.ModifierTypeHeal:
113 | return c.localHealModifiers.Expose()
114 | default:
115 | return []uint64{}
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/persistence/db.go:
--------------------------------------------------------------------------------
1 | package persistence
2 |
3 | import (
4 | "fmt"
5 | "github.com/sunist-c/genius-invokation-simulator-backend/model/localization"
6 | "os"
7 | "path"
8 | "time"
9 |
10 | "github.com/go-xorm/xorm"
11 | "xorm.io/core"
12 | )
13 |
14 | const (
15 | ruleSetPersistenceFileName = "rule-set-persistence.psc"
16 | cardPersistenceFileName = "card-persistence.psc"
17 | characterPersistenceFileName = "character-persistence.psc"
18 | skillPersistenceFileName = "skill-persistence.psc"
19 | summonPersistenceFileName = "summon-persistence.psc"
20 | eventPersistenceFileName = "event-persistence.psc"
21 | sqlite3DBFileName = "gisb-sqlite3.db"
22 | )
23 |
24 | var (
25 | storagePath = ""
26 | loaded = false
27 | )
28 |
29 | var (
30 | RuleSetPersistence = newFactoryPersistence[RuleSet]()
31 | CardPersistence = newFactoryPersistence[Card]()
32 | CharacterPersistence = newFactoryPersistence[Character]()
33 | SkillPersistence = newFactoryPersistence[Skill]()
34 | SummonPersistence = newFactoryPersistence[Summon]()
35 | EventPersistence = newFactoryPersistence[Event]()
36 |
37 | LocalizationPersistence = newMemoryCache[string, localization.LanguagePack]()
38 | ModInfoPersistence = newMemoryCache[string, ModInfo]()
39 | RoomInfoPersistence = newMemoryCache[uint64, RoomInfo]()
40 |
41 | TokenPersistence = newTimingMemoryCache[string, Token]()
42 |
43 | CardDeckPersistence DatabasePersistence[uint64, CardDeck]
44 | PlayerPersistence DatabasePersistence[uint64, Player]
45 | )
46 |
47 | // SetStoragePath 设置持久化文件的存放位置
48 | func SetStoragePath(path string) error {
49 | if s, err := os.Stat(path); err == nil && s.IsDir() {
50 | storagePath = path
51 | return nil
52 | } else {
53 | return fmt.Errorf("path %s is not a directory or is not exist", path)
54 | }
55 | }
56 |
57 | // Serve 开启持久化模块的服务
58 | func Serve(flushFeq time.Duration, errChan chan error) {
59 | RuleSetPersistence.Serve(flushFeq, storagePath, ruleSetPersistenceFileName, errChan)
60 | CardPersistence.Serve(flushFeq, storagePath, cardPersistenceFileName, errChan)
61 | CharacterPersistence.Serve(flushFeq, storagePath, characterPersistenceFileName, errChan)
62 | SkillPersistence.Serve(flushFeq, storagePath, skillPersistenceFileName, errChan)
63 | SummonPersistence.Serve(flushFeq, storagePath, summonPersistenceFileName, errChan)
64 | EventPersistence.Serve(flushFeq, storagePath, eventPersistenceFileName, errChan)
65 | TokenPersistence.Serve(time.Second*time.Duration(300), 0.5)
66 | }
67 |
68 | // Load 从持久化文件读取信息,写入持久化模块
69 | func Load(errChan chan error) {
70 | if !loaded {
71 | var err error
72 |
73 | // 初始化Sqlite3
74 | if sqlite3DB, err = xorm.NewEngine("sqlite3", path.Join(storagePath, sqlite3DBFileName)); err != nil {
75 | errChan <- err
76 | } else {
77 | sqlite3DB.SetMapper(core.SameMapper{})
78 |
79 | var success bool
80 | if success, CardDeckPersistence = newDatabasePersistence[uint64, CardDeck](errChan); !success {
81 | errChan <- fmt.Errorf("failed to create database factoryPersistence with entity: %+v", CardDeck{})
82 | }
83 |
84 | if success, PlayerPersistence = newDatabasePersistence[uint64, Player](errChan); !success {
85 | errChan <- fmt.Errorf("failed to create database factoryPersistence with entity: %+v", Player{})
86 | }
87 | }
88 |
89 | // 初始化Factories
90 | {
91 | if err = RuleSetPersistence.Load(path.Join(storagePath, ruleSetPersistenceFileName)); err != nil {
92 | errChan <- err
93 | }
94 |
95 | if err = CardPersistence.Load(path.Join(storagePath, cardPersistenceFileName)); err != nil {
96 | errChan <- err
97 | }
98 |
99 | if err = CharacterPersistence.Load(path.Join(storagePath, characterPersistenceFileName)); err != nil {
100 | errChan <- err
101 | }
102 |
103 | if err = SkillPersistence.Load(path.Join(storagePath, skillPersistenceFileName)); err != nil {
104 | errChan <- err
105 | }
106 |
107 | if err = SummonPersistence.Load(path.Join(storagePath, summonPersistenceFileName)); err != nil {
108 | errChan <- err
109 | }
110 |
111 | if err = EventPersistence.Load(path.Join(storagePath, eventPersistenceFileName)); err != nil {
112 | errChan <- err
113 | }
114 | }
115 |
116 | loaded = true
117 | }
118 | }
119 |
120 | // Quit 退出持久化模块的各种持久化服务
121 | func Quit() {
122 | RuleSetPersistence.Exit()
123 | CardPersistence.Exit()
124 | CharacterPersistence.Exit()
125 | SkillPersistence.Exit()
126 | SummonPersistence.Exit()
127 | EventPersistence.Exit()
128 | TokenPersistence.Exit()
129 | }
130 |
--------------------------------------------------------------------------------
/docs/en/README.md:
--------------------------------------------------------------------------------
1 | # 1. Summary
2 |
3 | ## 1.1 Overview
4 |
5 | Here is a simulator of the game Genshin Impact's `Genius Invokation TCG`, which is a backend implementation of the `Genius Invokation TCG`, playing methods referring to the version 3.3 of Genshin Impact, including all the game mechanisms in Genshin Impact, and expanding some content that miHoYo has not released.
6 |
7 | > **Attention: This readme file is for developers' reference. If you only want to use this project, you can explore the guide page of this website**
8 |
9 | ## 1.2 Announcement
10 |
11 | The community channel(QQ) of this project is `530924402`, you can also use the [GitHub Discussions](https://github.com/sunist-c/genius-invokation-simulator-backend/discussions) for communication. Welcome to discuss and PR :)
12 |
13 | This simulator focuses on providing customized `Genius Invokation TCG` game, such as:
14 |
15 | + Roll 12 dice each round
16 | + Obtain four cards each round
17 | + Play with customized roles, cards and rules
18 |
19 | As soon as the project is stable, we will optimize and adapt for AI training at first time, we consider to Compatible with `RLcard` when designing interfaces
20 |
21 | > **This project does not contain any art materials of Genshin Impact, and the relevant copyright of Genshin Impact belongs to miHoYo**
22 |
23 | ## 1.3 Technological Stacks and Components
24 |
25 | + golang 1.19 or higher: this is the coding language and running environment of the project
26 | + gcc: compile cgo requirements caused by embedded sqlite3
27 | + sqlite3: the persistent component used to store player information and player card set information in this project, it is embedded and does not need to be downloaded
28 |
29 | # 2. Badges
30 |
31 | ## 2.1 Build Status
32 |
33 | | Branch | master | dev | release |
34 | | :--: | :--: | :--: | :--: |
35 | | drone-ci | [](https://drone.sunist.cn/sunist-c/genius-invokation-simulator-backend) | [](https://drone.sunist.cn/sunist-c/genius-invokation-simulator-backend) | nil |
36 | | github-action | [](https://github.com/sunist-c/genius-invokation-simulator-backend/actions/workflows/go.yml) | [](https://github.com/sunist-c/genius-invokation-simulator-backend/actions/workflows/go.yml) | nil |
37 |
38 | ## 2.2 Code Analysis
39 |
40 | [](https://goreportcard.com/report/github.com/sunist-c/genius-invokation-simulator-backend)
41 |
42 | # 3. Branches
43 |
44 | - master: the main branch, will synchronize with the dev branch when important function changes occur
45 | - dev: the branch under development, will undergo frequent changes and new function tests
46 | - release: the stable branch, will only merge functions when major version is updated
47 | - document: the document branch, the source file of this website
48 |
49 | # 4. Progress
50 |
51 | Please turn to [gisb's feature development](https://github.com/users/sunist-c/projects/2) to explore the progress of this project
52 |
53 | # 5. Contribution
54 |
55 | > // todo
56 |
57 | # 6. Features
58 |
59 |
60 | - [ ] Basic game playing method
61 | - [ ] Elemental reaction
62 | - [x] Roles and skills
63 | - [ ] Artifacts and weapons
64 | - [ ] Scenarios and partners
65 | - [ ] Summons
66 | - [ ] Cards
67 | - [x] Element transformation
68 | - [ ] Game expansion
69 | - [ ] Multiplayer game(players more than 2)
70 | - [ ] Match game
71 | - [ ] Creat room
72 | - [ ] Join room
73 | - [ ] In-game communication
74 | - [ ] Viewer mode
75 | - [ ] Competition mode
76 | - [ ] Operational record
77 | - [x] Custom Mod support
78 | - [ ] Go/Lua/Python support
79 | - [x] Custom roles and cards
80 | - [x] Custom rules
81 | - [x] Multiple communication protocol connections
82 | - [x] websocket interface
83 | - [x] http/https interface
84 | - [ ] udp/kcp interface
85 | - [ ] rpc interface
86 | - [ ] Distributed support
87 | - [ ] Automated deployment
88 | - [ ] Service registration and service discovery
89 | - [ ] Server load balancing
90 | - [ ] Management function
91 | - [ ] Announcements and notices
92 | - [x] IP tracking/blocking
93 | - [x] QPS/TPS limiter
--------------------------------------------------------------------------------
/entity/player.go:
--------------------------------------------------------------------------------
1 | package entity
2 |
3 | import (
4 | "github.com/sunist-c/genius-invokation-simulator-backend/entity/model"
5 | "github.com/sunist-c/genius-invokation-simulator-backend/enum"
6 | "github.com/sunist-c/genius-invokation-simulator-backend/model/modifier/definition"
7 | )
8 |
9 | type PlayerInfo struct {
10 | UID uint64
11 | Cards []model.Card
12 | Characters []*character
13 | }
14 |
15 | type player struct {
16 | uid uint64 // uid 玩家的UID,由其他模块托管
17 | status enum.PlayerStatus // status 玩家的状态
18 |
19 | operated bool // operated 本回合玩家是否操作过
20 | reRollTimes uint // reRollTimes 重新投掷的次数
21 | staticCost *model.Cost // staticCost 每回合投掷阶段固定产出的骰子
22 |
23 | holdingCost *model.Cost // holdingCost 玩家持有的骰子
24 | cardDeck *CardDeck // cardDeck 玩家的牌堆
25 | holdingCards map[uint64]model.Card // holdingCards 玩家持有的卡牌
26 | activeCharacter uint64 // activeCharacter 玩家当前的前台角色
27 |
28 | characters map[uint64]*character // characters 玩家出战的角色
29 | characterList []uint64 // characterList 玩家的角色列表
30 | summons map[uint64]Summon // summons 玩家在场的召唤物
31 | summonList []uint64 // summonList 玩家的召唤物列表
32 | supports map[uint64]Support // supports 玩家在场的支援
33 | supportList []uint64 // supportList 玩家的支援物列表
34 |
35 | globalDirectAttackModifiers definition.AttackModifiers // globalDirectAttackModifiers 全局直接攻击修正
36 | globalFinalAttackModifiers definition.AttackModifiers // globalFinalAttackModifiers 全局最终攻击修正
37 | globalDefenceModifiers definition.DefenceModifiers // globalDefenceModifiers 全局防御修正
38 | globalHealModifiers definition.HealModifiers // globalHealModifiers 全局治疗修正
39 | globalChargeModifiers definition.ChargeModifiers // globalChargeModifiers 全局充能修正
40 | globalCostModifiers definition.CostModifiers // globalCostModifiers 全局费用修正
41 |
42 | cooperativeAttacks map[enum.TriggerType]model.CooperativeSkill // cooperativeAttacks 协同攻击技能
43 | callbackEvents *Map // callbackEvents 回调事件集合
44 | }
45 |
46 | func (p player) GetUID() (uid uint64) {
47 | return p.uid
48 | }
49 |
50 | func (p player) GetCost() (cost map[enum.ElementType]uint) {
51 | return p.holdingCost.Costs()
52 | }
53 |
54 | func (p player) GetCards() (cards []uint64) {
55 | cards = []uint64{}
56 | for i := range p.holdingCards {
57 | cards = append(cards, i)
58 | }
59 |
60 | return cards
61 | }
62 |
63 | func (p player) GetSummons() (summons []uint64) {
64 | return p.summonList
65 | }
66 |
67 | func (p player) GetSupports() (supports []uint64) {
68 | return p.summonList
69 | }
70 |
71 | func (p player) CardDeckRemain() (remain uint) {
72 | return p.cardDeck.remain
73 | }
74 |
75 | func (p player) GetActiveCharacter() (character uint64) {
76 | return p.activeCharacter
77 | }
78 |
79 | func (p player) GetBackgroundCharacters() (characters []uint64) {
80 | characters = []uint64{}
81 | for _, character := range p.characters {
82 | if character.status != enum.CharacterStatusDefeated && character.id != p.activeCharacter {
83 | characters = append(characters, character.id)
84 | }
85 | }
86 |
87 | return characters
88 | }
89 |
90 | func (p player) GetCharacter(character uint64) (has bool, entity model.Character) {
91 | characterEntity, exist := p.characters[character]
92 | return exist, characterEntity
93 | }
94 |
95 | func (p player) GetStatus() (status enum.PlayerStatus) {
96 | return p.status
97 | }
98 |
99 | func (p player) GetGlobalModifiers(modifierType enum.ModifierType) (modifiers []uint64) {
100 | switch modifierType {
101 | case enum.ModifierTypeNone:
102 | return []uint64{}
103 | case enum.ModifierTypeAttack:
104 | modifiers = p.globalDirectAttackModifiers.Expose()
105 | modifiers = append(modifiers, p.globalFinalAttackModifiers.Expose()...)
106 | return modifiers
107 | case enum.ModifierTypeCharacter:
108 | return []uint64{}
109 | case enum.ModifierTypeCharge:
110 | return p.globalChargeModifiers.Expose()
111 | case enum.ModifierTypeCost:
112 | return p.globalCostModifiers.Expose()
113 | case enum.ModifierTypeDefence:
114 | return p.globalDefenceModifiers.Expose()
115 | case enum.ModifierTypeHeal:
116 | return p.globalHealModifiers.Expose()
117 | default:
118 | return []uint64{}
119 | }
120 | }
121 |
122 | func (p player) GetCooperativeSkills(trigger enum.TriggerType) (skills []uint64) {
123 | return []uint64{}
124 | }
125 |
126 | func (p player) GetEvents(trigger enum.TriggerType) (events []uint64) {
127 | return p.callbackEvents.Expose(trigger)
128 | }
129 |
--------------------------------------------------------------------------------
/mod/implement/mod.go:
--------------------------------------------------------------------------------
1 | package implement
2 |
3 | import (
4 | "github.com/sunist-c/genius-invokation-simulator-backend/enum"
5 | "github.com/sunist-c/genius-invokation-simulator-backend/mod/definition"
6 | )
7 |
8 | type ModImpl struct {
9 | characters map[uint64]definition.Character
10 | skills map[uint64]definition.Skill
11 | events map[uint64]definition.Event
12 | summons map[uint64]definition.Summon
13 | cards map[uint64]definition.Card
14 | rules map[uint64]definition.Rule
15 | languages map[enum.Language]definition.LanguagePack
16 | }
17 |
18 | func (impl *ModImpl) ProduceCharacters() []definition.Character {
19 | if impl.characters == nil {
20 | impl.characters = map[uint64]definition.Character{}
21 | }
22 |
23 | result := make([]definition.Character, 0, len(impl.characters))
24 | for _, character := range impl.characters {
25 | result = append(result, character)
26 | }
27 |
28 | return result
29 | }
30 |
31 | func (impl *ModImpl) ProduceSkills() []definition.Skill {
32 | if impl.skills == nil {
33 | impl.skills = map[uint64]definition.Skill{}
34 | }
35 |
36 | result := make([]definition.Skill, 0, len(impl.skills))
37 | for _, skill := range impl.skills {
38 | result = append(result, skill)
39 | }
40 |
41 | return result
42 | }
43 |
44 | func (impl *ModImpl) ProduceEvents() []definition.Event {
45 | if impl.events == nil {
46 | impl.events = map[uint64]definition.Event{}
47 | }
48 |
49 | result := make([]definition.Event, 0, len(impl.events))
50 | for _, event := range impl.events {
51 | result = append(result, event)
52 | }
53 |
54 | return result
55 | }
56 |
57 | func (impl *ModImpl) ProduceSummons() []definition.Summon {
58 | if impl.summons == nil {
59 | impl.summons = map[uint64]definition.Summon{}
60 | }
61 |
62 | result := make([]definition.Summon, 0, len(impl.summons))
63 | for _, summon := range impl.summons {
64 | result = append(result, summon)
65 | }
66 |
67 | return result
68 | }
69 |
70 | func (impl *ModImpl) ProduceCards() []definition.Card {
71 | if impl.cards == nil {
72 | impl.cards = map[uint64]definition.Card{}
73 | }
74 |
75 | result := make([]definition.Card, 0, len(impl.cards))
76 | for _, card := range impl.cards {
77 | result = append(result, card)
78 | }
79 |
80 | return result
81 | }
82 |
83 | func (impl *ModImpl) ProduceRules() []definition.Rule {
84 | if impl.rules == nil {
85 | impl.rules = map[uint64]definition.Rule{}
86 | }
87 |
88 | result := make([]definition.Rule, 0, len(impl.rules))
89 | for _, rule := range impl.rules {
90 | result = append(result, rule)
91 | }
92 |
93 | return result
94 | }
95 |
96 | func (impl *ModImpl) ProduceLanguagePacks() []definition.LanguagePack {
97 | if impl.languages == nil {
98 | impl.languages = map[enum.Language]definition.LanguagePack{}
99 | }
100 |
101 | result := make([]definition.LanguagePack, 0, len(impl.languages))
102 | for _, languagePack := range impl.languages {
103 | result = append(result, languagePack)
104 | }
105 |
106 | return result
107 | }
108 |
109 | func (impl *ModImpl) RegisterCharacter(character definition.Character) {
110 | if impl.characters == nil {
111 | impl.characters = map[uint64]definition.Character{}
112 | }
113 |
114 | impl.characters[character.TypeID()] = character
115 | }
116 |
117 | func (impl *ModImpl) RegisterSkill(skill definition.Skill) {
118 | if impl.skills == nil {
119 | impl.skills = map[uint64]definition.Skill{}
120 | }
121 |
122 | impl.skills[skill.TypeID()] = skill
123 | }
124 |
125 | func (impl *ModImpl) RegisterEvent(event definition.Event) {
126 | if impl.events == nil {
127 | impl.events = map[uint64]definition.Event{}
128 | }
129 |
130 | impl.events[event.TypeID()] = event
131 | }
132 |
133 | func (impl *ModImpl) RegisterSummon(summon definition.Summon) {
134 | if impl.summons == nil {
135 | impl.summons = map[uint64]definition.Summon{}
136 | }
137 |
138 | impl.summons[summon.TypeID()] = summon
139 | }
140 |
141 | func (impl *ModImpl) RegisterCard(card definition.Card) {
142 | impl.cards[card.TypeID()] = card
143 | }
144 |
145 | func (impl *ModImpl) RegisterRule(rule definition.Rule) {
146 | impl.rules[rule.TypeID()] = rule
147 | }
148 |
149 | func (impl *ModImpl) AttachLanguagePack(languagePack definition.LanguagePack) {
150 | impl.languages[languagePack.Language()] = languagePack
151 | }
152 |
153 | func NewMod() definition.Mod {
154 | return &ModImpl{
155 | characters: map[uint64]definition.Character{},
156 | skills: map[uint64]definition.Skill{},
157 | events: map[uint64]definition.Event{},
158 | summons: map[uint64]definition.Summon{},
159 | cards: map[uint64]definition.Card{},
160 | rules: map[uint64]definition.Rule{},
161 | languages: map[enum.Language]definition.LanguagePack{},
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Genius Invokation Simulator Backend
2 |
3 | > 5.29: 项目还在更新,稍安勿躁,目前在研究老米的新版本
4 |
5 | ## 1. 综述
6 |
7 | ### 1.1 概况
8 |
9 | 这里是原神(Genshin Impact)的《七圣召唤》模拟器,是参考原神3.3版本的「七圣召唤」玩法重新实现的后端(服务端),包括所有的原神内的游戏内容,并拓展一些米哈游没有做的内容。
10 |
11 | **本自述文件供开发者参考,若您只是使用本项目,您可以转到我们的[文档](https://sunist-c.github.io/genius-invokation-simulator-backend/)**
12 |
13 | [English Version](https://sunist-c.github.io/genius-invokation-simulator-backend/#/en/)
14 |
15 | ### 1.2 声明
16 |
17 | 本项目的交流群(QQ)为`530924402`,欢迎讨论与PR
18 |
19 | 本模拟器侧重于提供自定义的七圣召唤对局,比如每次投十二个骰子/每回合摸四张牌/加入自定义角色、卡牌等功能,~~短期内没有~~ 项目稳定后尽快针对ai训练进行优化、适配。
20 | 考虑设计接口时兼容RLcard
21 |
22 | 相关的[genius-invokation-gym](https://github.com/paladin1013/genius-invokation-gym)项目侧重于提供ai相关接口,请根据需求选择
23 |
24 | **本模拟器接口尽量和[genius-invokation-gym](https://github.com/paladin1013/genius-invokation-gym)保持一致,其项目完善后本项目也尽量拓展相应的ai接口**
25 |
26 | 同时感谢[@Leng Yue](https://github.com/leng-yue)实现的前端项目[genius-invokation-webui](https://github.com/leng-yue/genius-invokation-webui)
27 |
28 | **本项目不含任何原神(Genshin Impact)的美术素材,原神(Genshin Impact)的相关版权归米哈游(miHoYo)所有**
29 |
30 | ### 1.3 技术栈与组件
31 |
32 | + golang 1.19: 这是本项目的编码语言与运行环境
33 | + mingw-gcc/clang/gcc: 因为内嵌sqlite3产生的cgo编译需求
34 | + sqlite3: 这是本项目用于存储玩家信息、玩家卡组信息的持久化组件,已内嵌,无需下载
35 |
36 | ## 2. 徽章
37 |
38 | ### 2.1 构建情况
39 |
40 | | Branch | master | dev | release |
41 | | :--: | :--: | :--: | :--: |
42 | | drone-ci | [](https://drone.sunist.cn/sunist-c/genius-invokation-simulator-backend) | [](https://drone.sunist.cn/sunist-c/genius-invokation-simulator-backend) | [](https://drone.sunist.cn/sunist-c/genius-invokation-simulator-backend) |
43 | | github-action | [](https://github.com/sunist-c/genius-invokation-simulator-backend/actions/workflows/go.yml) | [](https://github.com/sunist-c/genius-invokation-simulator-backend/actions/workflows/go.yml) | [](https://github.com/sunist-c/genius-invokation-simulator-backend/actions/workflows/go.yml) |
44 |
45 | ### 2.2 代码质量
46 |
47 | [](https://goreportcard.com/report/github.com/sunist-c/genius-invokation-simulator-backend)
48 |
49 | ## 3. 分支说明
50 |
51 | - master 主要的分支,将在发生重要功能修改时与dev分支进行同步
52 | - dev 开发中的分支,将频繁地进行更改与新功能测试
53 | - release 稳定的分支,仅会在重大版本更新时进行功能合并
54 | - cli 脚手架分支,将提供一个代码生成脚手架,用于快速实现mod
55 |
56 | ## 4. 开发进度
57 |
58 | 请转到[gisb's feature development](https://github.com/users/sunist-c/projects/2)查看项目进度
59 |
60 | ## 5. 开发文档
61 |
62 | 您可以转到[genius-invokation-simulator-mod-template](https://github.com/sunist-c/genius-invokation-simulator-mod-template)查看我们的示例mod,并从该模板创建您的mod
63 |
64 | + 战斗框架: [Battle Framework of GISB](https://github.com/sunist-c/genius-invokation-simulator-backend/wiki/Battle-Framework)
65 | + 事件框架: Mkdir...
66 | + MOD制作: Mkdir...
67 |
68 | ## 6. 参与项目
69 |
70 | 如果您想增加一个功能或想法:
71 |
72 | 1. 加入本项目的交流群或[genius-invokation-gym](https://github.com/paladin1013/genius-invokation-gym)的交流群或在本项目的[Discussion/Ideas](https://github.com/sunist-c/genius-invokation-simulator-backend/discussions/categories/ideas)中分享您的想法
73 | 2. 在取得Contributor的广泛认可后将为您创建一个WIP的issue
74 | 3. 按照正常的流程fork->coding->pull request您的修改
75 |
76 | 如果您想为项目贡献代码,您可以转到[gisb's feature development](https://github.com/users/sunist-c/projects/2)查看这个项目目前在干什么,标有`help wanted`的内容可能需要一些帮助,处于`design`阶段的内容目前还没有进行开发,您可以直接在GitHub Projects/Project Issue页面与我们交流
77 |
78 | ## 7. 功能特性
79 |
80 | 转到我们的[文档](https://sunist-c.github.io/genius-invokation-simulator-backend/)查看最新的功能特性
81 |
82 | ## 8. 开源许可
83 |
84 | [MIT License](license)
85 |
86 | ## 9. 鸣谢
87 |
88 | 感谢 [JetBrains](https://www.jetbrains.com) 为本项目提供的 [Open Source development license(s)](https://www.jetbrains.com/community/opensource/#support)
89 |
90 | 


91 |
--------------------------------------------------------------------------------
/protocol/websocket/message/action.go:
--------------------------------------------------------------------------------
1 | package message
2 |
3 | import (
4 | "github.com/sunist-c/genius-invokation-simulator-backend/enum"
5 | )
6 |
7 | // ActionMessageInterface 玩家操作信息的约束接口
8 | type ActionMessageInterface interface {
9 | AttackAction | BurnCardAction | UseCardAction | ReRollAction | SkipRoundAction | SwitchAction | ConcedeAction
10 | }
11 |
12 | // AttackAction 玩家的攻击操作信息
13 | type AttackAction struct {
14 | Sender uint64 `json:"sender" yaml:"sender" xml:"sender"` // Sender 发起操作的玩家
15 | Target uint64 `json:"target" yaml:"target" xml:"target"` // Target 被攻击的玩家
16 | Skill uint64 `json:"skill" yaml:"skill" xml:"skill"` // Skill 执行攻击的技能
17 | Paid map[enum.ElementType]uint `json:"paid" yaml:"paid" xml:"paid"` // Paid 支付发起攻击的费用
18 | }
19 |
20 | // BurnCardAction 玩家的元素转换操作信息
21 | type BurnCardAction struct {
22 | Sender uint64 `json:"sender" yaml:"sender" xml:"sender"` // Sender 发起操作的玩家
23 | Card uint64 `json:"card" yaml:"card" xml:"card"` // Card 被操作的卡牌
24 | Paid enum.ElementType `json:"paid" yaml:"paid" xml:"paid"` // Paid 支付元素转换的费用
25 | }
26 |
27 | // ReRollAction 玩家的重掷元素骰子操作信息
28 | type ReRollAction struct {
29 | Sender uint64 `json:"sender" yaml:"sender" xml:"sender"` // Sender 发起操作的玩家
30 | Dropped map[enum.ElementType]uint `json:"dropped" yaml:"dropped" xml:"dropped"` // Dropped 被舍弃的元素骰子
31 | }
32 |
33 | // UseCardAction 玩家的使用卡牌操作信息
34 | type UseCardAction struct {
35 | Sender uint64 `json:"sender" yaml:"sender" xml:"sender"` // Sender 发起操作的玩家
36 | Card uint64 `json:"card" yaml:"card" xml:"card"` // Card 玩家打出的卡牌
37 | Paid map[enum.ElementType]uint `json:"paid" yaml:"paid" xml:"paid"` // Paid 玩家打出卡牌支付的费用
38 | }
39 |
40 | // SkipRoundAction 玩家的跳过回合操作信息
41 | type SkipRoundAction struct {
42 | Sender uint64 `json:"sender" yaml:"sender" xml:"sender"` // Sender 发起操作的玩家
43 | }
44 |
45 | // SwitchAction 玩家的切换前台角色操作信息
46 | type SwitchAction struct {
47 | Sender uint64 `json:"sender" yaml:"sender" xml:"sender"` // Sender 发起操作的玩家
48 | Target uint64 `json:"target" yaml:"target" xml:"target"` // Target 切换到的目标角色
49 | Paid map[enum.ElementType]uint `json:"paid" yaml:"paid" xml:"paid"` // Paid 玩家切换角色支付的费用
50 | }
51 |
52 | // ConcedeAction 玩家的弃权操作信息
53 | type ConcedeAction struct {
54 | Sender uint64 `json:"sender" yaml:"sender" xml:"sender"` // Sender 发起操作的玩家
55 | }
56 |
57 | // ActionMessage 玩家的操作信息
58 | type ActionMessage struct {
59 | Type enum.ActionType `json:"type" yaml:"type" xml:"type"` // Type 玩家的操作类型
60 | Sender uint64 `json:"sender" yaml:"sender" xml:"sender"` // Sender 发送操作的玩家
61 | Args interface{} `json:"args" yaml:"args" xml:"args"` // Args 玩家的操作信息
62 | }
63 |
64 | func exchangeMessage[target ActionMessageInterface](message ActionMessage, conditions ...enum.ActionType) (success bool, result target) {
65 | typeCheck := false
66 | for _, condition := range conditions {
67 | if message.Type == condition {
68 | typeCheck = true
69 | break
70 | }
71 | }
72 |
73 | if !typeCheck {
74 | return false, result
75 | } else {
76 | result, success = message.Args.(target)
77 | return success, result
78 | }
79 | }
80 |
81 | // ToAttackMessage 将ActionMessage转换为AttackAction
82 | func (a ActionMessage) ToAttackMessage() (success bool, message AttackAction) {
83 | return exchangeMessage[AttackAction](a, enum.ActionNormalAttack, enum.ActionElementalSkill, enum.ActionElementalBurst)
84 | }
85 |
86 | // ToBurnCardMessage 将ActionMessage转换为BurnCardAction
87 | func (a ActionMessage) ToBurnCardMessage() (success bool, message BurnCardAction) {
88 | return exchangeMessage[BurnCardAction](a, enum.ActionBurnCard)
89 | }
90 |
91 | // ToUesCardMessage 将ActionMessage转换为UseCardAction
92 | func (a ActionMessage) ToUesCardMessage() (success bool, message UseCardAction) {
93 | return exchangeMessage[UseCardAction](a, enum.ActionUseCard)
94 | }
95 |
96 | // ToReRollMessage 将ActionMessage转换为ReRollAction
97 | func (a ActionMessage) ToReRollMessage() (success bool, message ReRollAction) {
98 | return exchangeMessage[ReRollAction](a, enum.ActionReRoll)
99 | }
100 |
101 | // ToSwitchMessage 将ActionMessage转换为SwitchAction
102 | func (a ActionMessage) ToSwitchMessage() (success bool, message SwitchAction) {
103 | return exchangeMessage[SwitchAction](a, enum.ActionSwitch)
104 | }
105 |
106 | // ToConcedeMessage 将ActionMessage转换为ConcedeAction
107 | func (a ActionMessage) ToConcedeMessage() (success bool, message ConcedeAction) {
108 | return exchangeMessage[ConcedeAction](a, enum.ActionConcede)
109 | }
110 |
111 | // ToSkipRoundMessage 将ActionMessage转换为SkipRoundAction
112 | func (a ActionMessage) ToSkipRoundMessage() (success bool, message SkipRoundAction) {
113 | return exchangeMessage[SkipRoundAction](a, enum.ActionSkipRound)
114 | }
115 |
--------------------------------------------------------------------------------
/persistence/timing.go:
--------------------------------------------------------------------------------
1 | package persistence
2 |
3 | import (
4 | "math/rand"
5 | "sync"
6 | "time"
7 | )
8 |
9 | var (
10 | random = rand.New(rand.NewSource(time.Now().UnixNano()))
11 | )
12 |
13 | type timingCacheRecord[T any] struct {
14 | data T
15 | timeoutAt time.Time
16 | }
17 |
18 | // timingMemoryCache 带超时系统的的内存缓存Map,实现同kv.syncMap
19 | type timingMemoryCache[PK comparable, T any] struct {
20 | mutex sync.RWMutex
21 | cache map[PK]*timingCacheRecord[T]
22 | size uint
23 | exit chan struct{}
24 | served bool
25 | }
26 |
27 | func (t *timingMemoryCache[PK, T]) proactivelyClean(index float64) {
28 | if index < 0 || index > 1 {
29 | index = 1
30 | }
31 |
32 | need, executeCount := uint(float64(t.size)/index), uint(0)
33 | executeList := make([]PK, need)
34 |
35 | // 统计需要删除的超时记录
36 | t.mutex.RLock()
37 | for pk, entity := range t.cache {
38 | if !entity.timeoutAt.IsZero() && entity.timeoutAt.Before(time.Now()) {
39 | executeList[executeCount] = pk
40 | executeCount++
41 | }
42 |
43 | if need = need - 1; need == 0 {
44 | break
45 | }
46 | }
47 | t.mutex.RUnlock()
48 |
49 | // 执行删除操作,频繁加锁性能较差,此处不采用分段锁
50 | t.mutex.Lock()
51 | for i := uint(0); i < executeCount; i++ {
52 | delete(t.cache, executeList[i])
53 | t.size -= 1
54 | }
55 | t.mutex.Unlock()
56 | }
57 |
58 | func (t *timingMemoryCache[PK, T]) serve(proactivelyCleanTime time.Duration, proactivelyCleanIndex float64) {
59 | // 起服务前先随机sleep一会,让一起初始化的多个组建主动清理时间错开
60 | randomNum := random.Int63n(int64(proactivelyCleanTime))
61 | time.Sleep(time.Duration(randomNum))
62 |
63 | for {
64 | select {
65 | case <-t.exit:
66 | return
67 | default:
68 | t.proactivelyClean(proactivelyCleanIndex)
69 | time.Sleep(proactivelyCleanTime)
70 | }
71 | }
72 | }
73 |
74 | func (t *timingMemoryCache[PK, T]) get(id PK) (exist bool, record *timingCacheRecord[T]) {
75 | t.mutex.RLock()
76 | r, has := t.cache[id]
77 | t.mutex.RUnlock()
78 | if !has {
79 | return false, record
80 | } else if !r.timeoutAt.IsZero() && r.timeoutAt.Before(time.Now()) {
81 | t.mutex.Lock()
82 | delete(t.cache, id)
83 | t.size -= 1
84 | t.mutex.Unlock()
85 | return false, record
86 | } else {
87 | return true, r
88 | }
89 | }
90 |
91 | func (t *timingMemoryCache[PK, T]) QueryByID(id PK) (has bool, result T, timeoutAt time.Time) {
92 | if exist, record := t.get(id); !exist {
93 | return false, result, time.Time{}
94 | } else {
95 | return true, record.data, record.timeoutAt
96 | }
97 | }
98 |
99 | func (t *timingMemoryCache[PK, T]) UpdateByID(id PK, entity T) (success bool, timeoutAt time.Time) {
100 | if exist, record := t.get(id); !exist {
101 | return false, time.Time{}
102 | } else {
103 | record.data = entity
104 | return true, record.timeoutAt
105 | }
106 | }
107 |
108 | func (t *timingMemoryCache[PK, T]) RefreshByID(id PK, timeout time.Duration) (success bool, timeoutAt time.Time) {
109 | if exist, record := t.get(id); !exist {
110 | return false, time.Time{}
111 | } else if timeout == 0 {
112 | record.timeoutAt = time.Time{}
113 | return true, record.timeoutAt
114 | } else {
115 | record.timeoutAt = time.Now().Add(timeout)
116 | return true, record.timeoutAt
117 | }
118 | }
119 |
120 | func (t *timingMemoryCache[PK, T]) InsertOne(id PK, entity T, timeout time.Duration) (success bool, timeoutAt time.Time) {
121 | if exist, _ := t.get(id); exist {
122 | return false, time.Time{}
123 | } else {
124 | if timeout == 0 {
125 | t.mutex.Lock()
126 | t.cache[id] = &timingCacheRecord[T]{data: entity, timeoutAt: time.Time{}}
127 | t.size += 1
128 | t.mutex.Unlock()
129 | return true, time.Time{}
130 | } else {
131 | timeoutAt = time.Now().Add(timeout)
132 | t.mutex.Lock()
133 | t.cache[id] = &timingCacheRecord[T]{data: entity, timeoutAt: timeoutAt}
134 | t.size += 1
135 | t.mutex.Unlock()
136 | return true, timeoutAt
137 | }
138 | }
139 | }
140 |
141 | func (t *timingMemoryCache[PK, T]) DeleteByID(id PK) (success bool) {
142 | if exist, _ := t.get(id); exist {
143 | t.mutex.Lock()
144 | delete(t.cache, id)
145 | t.size -= 1
146 | t.mutex.Unlock()
147 | return true
148 | } else {
149 | return false
150 | }
151 | }
152 |
153 | func (t *timingMemoryCache[PK, T]) Serve(proactivelyCleanTime time.Duration, proactivelyCleanIndex float64) {
154 | if proactivelyCleanTime > 0 {
155 | go t.serve(proactivelyCleanTime, proactivelyCleanIndex)
156 | t.served = true
157 | } else {
158 | t.served = false
159 | }
160 | }
161 |
162 | func (t *timingMemoryCache[PK, T]) Exit() {
163 | if t.served {
164 | t.exit <- struct{}{}
165 | }
166 | }
167 |
168 | func newTimingMemoryCache[PK comparable, T any]() TimingMemoryCache[PK, T] {
169 | return &timingMemoryCache[PK, T]{
170 | mutex: sync.RWMutex{},
171 | cache: map[PK]*timingCacheRecord[T]{},
172 | size: 0,
173 | exit: make(chan struct{}, 1),
174 | served: false,
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/mod/adapter/card.go:
--------------------------------------------------------------------------------
1 | package adapter
2 |
3 | import (
4 | "github.com/sunist-c/genius-invokation-simulator-backend/entity/model"
5 | "github.com/sunist-c/genius-invokation-simulator-backend/enum"
6 | definition "github.com/sunist-c/genius-invokation-simulator-backend/mod/definition"
7 | "github.com/sunist-c/genius-invokation-simulator-backend/mod/implement"
8 | "github.com/sunist-c/genius-invokation-simulator-backend/model/adapter"
9 | )
10 |
11 | type cardAdapterLayer struct {
12 | implement.EntityImpl
13 | cardType enum.CardType
14 | cost map[enum.ElementType]uint
15 | }
16 |
17 | func (c *cardAdapterLayer) Type() enum.CardType {
18 | return c.cardType
19 | }
20 |
21 | func (c *cardAdapterLayer) Cost() map[enum.ElementType]uint {
22 | return c.cost
23 | }
24 |
25 | type CardAdapter struct{}
26 |
27 | func (c CardAdapter) Convert(source definition.Card) (success bool, result model.Card) {
28 | adapterLayer := &cardAdapterLayer{
29 | EntityImpl: implement.EntityImpl{},
30 | cardType: source.CardType(),
31 | cost: source.Cost(),
32 | }
33 |
34 | return true, adapterLayer
35 | }
36 |
37 | func NewCardAdapter() adapter.Adapter[definition.Card, model.Card] {
38 | return CardAdapter{}
39 | }
40 |
41 | type eventCardAdapterLayer struct {
42 | cardAdapterLayer
43 | event definition.Event
44 | }
45 |
46 | func (e *eventCardAdapterLayer) Event() (event model.Event) {
47 | _, event = eventAdapter.Convert(e.event)
48 | return event
49 | }
50 |
51 | type EventCardAdapter struct{}
52 |
53 | func (e EventCardAdapter) Convert(source definition.EventCard) (success bool, result model.EventCard) {
54 | adapterLayer := &eventCardAdapterLayer{
55 | cardAdapterLayer: cardAdapterLayer{
56 | EntityImpl: implement.EntityImpl{},
57 | cardType: source.CardType(),
58 | cost: source.Cost(),
59 | },
60 | event: source.Event(),
61 | }
62 |
63 | return true, adapterLayer
64 | }
65 |
66 | func NewEventCardAdapter() adapter.Adapter[definition.EventCard, model.EventCard] {
67 | return EventCardAdapter{}
68 | }
69 |
70 | type supportCardAdapterLayer struct {
71 | cardAdapterLayer
72 | event definition.Event
73 | }
74 |
75 | func (s *supportCardAdapterLayer) Support() (event model.Event) {
76 | _, event = eventAdapter.Convert(s.event)
77 | return event
78 | }
79 |
80 | type SupportCardAdapter struct{}
81 |
82 | func (s SupportCardAdapter) Convert(source definition.SupportCard) (success bool, result model.SupportCard) {
83 | adapterLayer := &supportCardAdapterLayer{
84 | cardAdapterLayer: cardAdapterLayer{
85 | EntityImpl: implement.EntityImpl{},
86 | cardType: source.CardType(),
87 | cost: source.Cost(),
88 | },
89 | event: source.Support(),
90 | }
91 |
92 | return true, adapterLayer
93 | }
94 |
95 | func NewSupportCardAdapter() adapter.Adapter[definition.SupportCard, model.SupportCard] {
96 | return SupportCardAdapter{}
97 | }
98 |
99 | type equipmentCardAdapterLayer struct {
100 | cardAdapterLayer
101 | event definition.Event
102 | equipmentType enum.EquipmentType
103 | }
104 |
105 | func (e *equipmentCardAdapterLayer) EquipmentType() enum.EquipmentType {
106 | return e.equipmentType
107 | }
108 |
109 | func (e *equipmentCardAdapterLayer) Modify() (event model.Event) {
110 | _, event = eventAdapter.Convert(e.event)
111 | return event
112 | }
113 |
114 | type EquipmentCardAdapter struct{}
115 |
116 | func (e EquipmentCardAdapter) Convert(source definition.EquipmentCard) (success bool, result model.EquipmentCard) {
117 | adapterLayer := &equipmentCardAdapterLayer{
118 | cardAdapterLayer: cardAdapterLayer{
119 | EntityImpl: implement.EntityImpl{},
120 | cardType: source.CardType(),
121 | cost: source.Cost(),
122 | },
123 | event: source.Modify(),
124 | equipmentType: source.EquipmentType(),
125 | }
126 |
127 | return true, adapterLayer
128 | }
129 |
130 | func NewEquipmentCardAdapter() adapter.Adapter[definition.EquipmentCard, model.EquipmentCard] {
131 | return EquipmentCardAdapter{}
132 | }
133 |
134 | type weaponCardAdapterLayer struct {
135 | equipmentCardAdapterLayer
136 | weaponType enum.WeaponType
137 | }
138 |
139 | func (w *weaponCardAdapterLayer) WeaponType() enum.WeaponType {
140 | return w.weaponType
141 | }
142 |
143 | type WeaponCardAdapter struct{}
144 |
145 | func (w WeaponCardAdapter) Convert(source definition.WeaponCard) (success bool, result model.WeaponCard) {
146 | adapterLayer := &weaponCardAdapterLayer{
147 | equipmentCardAdapterLayer: equipmentCardAdapterLayer{
148 | cardAdapterLayer: cardAdapterLayer{
149 | EntityImpl: implement.EntityImpl{},
150 | cardType: source.CardType(),
151 | cost: source.Cost(),
152 | },
153 | event: source.Modify(),
154 | equipmentType: source.EquipmentType(),
155 | },
156 | weaponType: source.WeaponType(),
157 | }
158 |
159 | return true, adapterLayer
160 | }
161 |
162 | func NewWeaponCardAdapter() adapter.Adapter[definition.WeaponCard, model.WeaponCard] {
163 | return WeaponCardAdapter{}
164 | }
165 |
--------------------------------------------------------------------------------
/protocol/http/util/preprocess.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "encoding/json"
5 | "github.com/gin-gonic/gin"
6 | "strconv"
7 | "strings"
8 | )
9 |
10 | func convertToInt(source string) (success bool, result int) {
11 | if intResult, err := strconv.Atoi(source); err != nil {
12 | return false, 0
13 | } else {
14 | return true, intResult
15 | }
16 | }
17 |
18 | func convertToFloat(source string) (success bool, result float64) {
19 | if floatResult, err := strconv.ParseFloat(source, 64); err != nil {
20 | return false, float64(0)
21 | } else {
22 | return true, floatResult
23 | }
24 | }
25 |
26 | func convertToBool(source string) (success bool, result bool) {
27 | if boolResult, err := strconv.ParseBool(source); err != nil {
28 | return false, false
29 | } else {
30 | return true, boolResult
31 | }
32 | }
33 |
34 | func convertToUint64(source string) (success bool, result uint64) {
35 | if uint64Result, err := strconv.ParseUint(source, 10, 64); err != nil {
36 | return false, 0
37 | } else {
38 | return true, uint64Result
39 | }
40 | }
41 |
42 | func convertToStruct[T any](source string) (success bool, result T) {
43 | if err := json.Unmarshal([]byte(source), &result); err != nil {
44 | return false, result
45 | } else {
46 | return true, result
47 | }
48 | }
49 |
50 | // BindJson 将请求体中的数据绑定到给定的entity中,entity需要可寻址,失败直接返回BadRequest
51 | func BindJson[T any](ctx *gin.Context, entity T) (success bool) {
52 | if err := ctx.ShouldBindJSON(entity); err != nil {
53 | return false
54 | } else {
55 | return true
56 | }
57 | }
58 |
59 | // QueryPath 从URL进行查询,支持路径参数与查询参数
60 | func QueryPath(ctx *gin.Context, key string) (has bool, result string) {
61 | if strings.HasPrefix(key, ":") {
62 | result, has = ctx.Params.Get(strings.Trim(key, ":"))
63 | } else {
64 | result, has = ctx.GetQuery(key)
65 | }
66 |
67 | return has, result
68 | }
69 |
70 | // QueryPathInt 从URL中查询一个int类型的结果,调用QueryPath并进行转化
71 | func QueryPathInt(ctx *gin.Context, key string) (has bool, result int) {
72 | if gotten, stringResult := QueryPath(ctx, key); !gotten {
73 | return false, 0
74 | } else {
75 | return convertToInt(stringResult)
76 | }
77 | }
78 |
79 | func QueryPathUint64(ctx *gin.Context, key string) (has bool, result uint64) {
80 | if gotten, stringResult := QueryPath(ctx, key); !gotten {
81 | return false, 0
82 | } else {
83 | return convertToUint64(stringResult)
84 | }
85 | }
86 |
87 | // QueryPathFloat 从URL中查询一个float64类型的结果,调QueryPathQueryPath并进行转化
88 | func QueryPathFloat(ctx *gin.Context, key string) (has bool, result float64) {
89 | if gotten, stringResult := QueryPath(ctx, key); !gotten {
90 | return false, float64(0)
91 | } else {
92 | return convertToFloat(stringResult)
93 | }
94 | }
95 |
96 | // QueryPathBool 从URL中查询一个bool类型的结果,调QueryPathQueryPath并进行转化
97 | func QueryPathBool(ctx *gin.Context, key string) (has bool, result bool) {
98 | if gotten, stringResult := QueryPath(ctx, key); !gotten {
99 | return false, false
100 | } else {
101 | return convertToBool(stringResult)
102 | }
103 | }
104 |
105 | // QueryPathJson 从URL中查询一个json对象,调用QueryPath并进行转化
106 | func QueryPathJson[T any](ctx *gin.Context, key string) (has bool, result T) {
107 | if gotten, stringResult := QueryPath(ctx, key); !gotten {
108 | return false, result
109 | } else {
110 | return convertToStruct[T](stringResult)
111 | }
112 | }
113 |
114 | // QueryCookie 从cookie中获取指定key的值
115 | func QueryCookie(ctx *gin.Context, key string) (has bool, cookie string) {
116 | if cookieResult, err := ctx.Cookie(key); err != nil {
117 | return false, cookie
118 | } else {
119 | return true, cookieResult
120 | }
121 | }
122 |
123 | // QueryHeaders 从请求头中获取指定key的值
124 | func QueryHeaders(ctx *gin.Context, key string) (has bool, result string) {
125 | if result = ctx.GetHeader(key); result == "" {
126 | return false, ""
127 | } else {
128 | return true, result
129 | }
130 | }
131 |
132 | // QueryHeadersInt 从请求头中获取一个int类型的值
133 | func QueryHeadersInt(ctx *gin.Context, key string) (has bool, result int) {
134 | if gotten, stringResult := QueryHeaders(ctx, key); !gotten {
135 | return false, 0
136 | } else {
137 | return convertToInt(stringResult)
138 | }
139 | }
140 |
141 | // QueryHeadersFloat 从请求头中获取一个float类型的值
142 | func QueryHeadersFloat(ctx *gin.Context, key string) (has bool, result float64) {
143 | if gotten, stringResult := QueryHeaders(ctx, key); !gotten {
144 | return false, float64(0)
145 | } else {
146 | return convertToFloat(stringResult)
147 | }
148 | }
149 |
150 | // QueryHeadersBool 从请求头中获取一个bool类型的值
151 | func QueryHeadersBool(ctx *gin.Context, key string) (has bool, result bool) {
152 | if gotten, stringResult := QueryHeaders(ctx, key); !gotten {
153 | return false, false
154 | } else {
155 | return convertToBool(stringResult)
156 | }
157 | }
158 |
159 | // QueryHeadersJson 从请求头中获取一个对象
160 | func QueryHeadersJson[T any](ctx *gin.Context, key string) (has bool, result T) {
161 | if gotten, stringResult := QueryHeaders(ctx, key); !gotten {
162 | return false, result
163 | } else {
164 | return convertToStruct[T](stringResult)
165 | }
166 | }
167 |
168 | // GetClientIP 获取请求客户端的IP
169 | func GetClientIP(ctx *gin.Context) (result string) {
170 | if addr := ctx.ClientIP(); addr == "::1" {
171 | return "127.0.0.1"
172 | } else {
173 | return addr
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/docs/development/api/card_deck.md:
--------------------------------------------------------------------------------
1 | 此处为GISB的卡组相关服务,包含卡组的新增、修改、获取、删除等接口
2 |
3 | # 变量与参数
4 |
5 | + `:card_deck_id`: 表示卡组的ID
6 | + `{gisb_token_id_value}`: 表示token_id在cookie中的值,由登录接口获取
7 | + `{gisb_token_value}`: 表示token在cookie中的键,由登录接口获取
8 |
9 | # 说明
10 |
11 | 本文的所有接口均需要鉴权,您需要在请求中附带您的token
12 |
13 | # 上传卡组
14 |
15 | ## 请求方法与地址
16 |
17 | ```plain text
18 | POST {BaseURL}/card_deck
19 | ```
20 |
21 | ## 请求/响应
22 |
23 |
24 |
25 |
26 |
27 | ### 请求
28 |
29 | 您需要提供所要上传卡组的信息
30 |
31 | #### 请求体定义
32 |
33 | ```go
34 | type UploadCardDeckRequest struct {
35 | Owner uint `json:"owner"`
36 | RequiredPackage []string `json:"required_package"`
37 | Cards []uint `json:"cards"`
38 | Characters []uint `json:"characters"`
39 | }
40 | ```
41 |
42 | #### 请求示例
43 |
44 | ```http
45 | POST /card_deck HTTP/1.1
46 | Host: {BaseURL}
47 | Content-Type: application/json
48 | Cookie: gisb_token={gisb_token_value}; gisb_token_id={gisb_token_id_value}
49 | Content-Length: 98
50 |
51 | {
52 | "owner": 1,
53 | "required_package": ["base"],
54 | "cards": [1],
55 | "characters": [1]
56 | }
57 | ```
58 |
59 |
60 |
61 | ### 响应
62 |
63 | 此接口将尝试把您提供的卡组上传至持久化模块,并同步更新玩家信息
64 |
65 | #### 响应体定义
66 |
67 | ```go
68 | type UploadCardDeckResponse struct {
69 | ID uint `json:"id"`
70 | Owner uint `json:"owner"`
71 | RequiredPackage []string `json:"required_package"`
72 | Cards []uint `json:"cards"`
73 | Characters []uint `json:"characters"`
74 | }
75 | ```
76 |
77 | #### 响应示例
78 |
79 | ```json
80 | {
81 | "id": 3,
82 | "owner": 1,
83 | "required_package": [
84 | "base"
85 | ],
86 | "cards": [
87 | 1
88 | ],
89 | "characters": [
90 | 1
91 | ]
92 | }
93 | ```
94 |
95 |
96 |
97 | # 获取卡组
98 |
99 | ## 请求方法与地址
100 |
101 | ```plain text
102 | GET {BaseURL}/card_deck/:card_deck_id
103 | ```
104 |
105 | ## 请求/响应
106 |
107 |
108 |
109 |
110 |
111 | ### 请求
112 |
113 | 本接口不需要提供请求体
114 |
115 | #### 请求示例
116 |
117 | ```http
118 | GET /card_deck/:card_deck_id HTTP/1.1
119 | Host: {BaseURL}
120 | Cookie: gisb_token={gisb_token_value}; gisb_token_id={gisb_token_id_value}
121 | ```
122 |
123 |
124 |
125 | ### 响应
126 |
127 | 此接口将查询并返回您所给定id的卡组
128 |
129 | #### 响应体定义
130 |
131 | ```go
132 | type QueryCardDeckResponse struct {
133 | ID uint `json:"id"`
134 | Owner uint `json:"owner"`
135 | RequiredPackage []string `json:"required_package"`
136 | Cards []uint `json:"cards"`
137 | Characters []uint `json:"characters"`
138 | }
139 | ```
140 |
141 | #### 响应示例
142 |
143 | ```json
144 | {
145 | "id": 3,
146 | "owner": 1,
147 | "required_package": [
148 | "base"
149 | ],
150 | "cards": [
151 | 1
152 | ],
153 | "characters": [
154 | 1
155 | ]
156 | }
157 | ```
158 |
159 |
160 |
161 | # 更新卡组
162 |
163 | ## 请求方法与地址
164 |
165 | ```plain text
166 | PUT {BaseURL}/card_deck/:card_deck_id
167 | ```
168 |
169 | ## 请求/响应
170 |
171 |
172 |
173 |
174 |
175 | ### 请求
176 |
177 | 您需要提供所要更新卡组的信息
178 |
179 | #### 请求体定义
180 |
181 | ```go
182 | type UpdateCardDeckRequest struct {
183 | Owner uint `json:"owner"`
184 | RequiredPackage []string `json:"required_package"`
185 | Cards []uint `json:"cards"`
186 | Characters []uint `json:"characters"`
187 | }
188 | ```
189 |
190 | #### 请求示例
191 |
192 | ```http
193 | PUT /card_deck/:card_deck_id HTTP/1.1
194 | Host: {BaseURL}
195 | Content-Type: application/json
196 | Cookie: gisb_token={gisb_token_value}; gisb_token_id={gisb_token_id_value}
197 | Content-Length: 101
198 |
199 | {
200 | "owner": 1,
201 | "required_package": ["enhance"],
202 | "cards": [1],
203 | "characters": [1]
204 | }
205 | ```
206 |
207 |
208 |
209 | ### 响应
210 |
211 | 此接口将根据您所给定的id尝试更新卡组,并将结果返回
212 |
213 | #### 响应体定义
214 |
215 | ```go
216 | type UpdateCardDeckResponse struct {
217 | ID uint `json:"id"`
218 | Owner uint `json:"owner"`
219 | RequiredPackage []string `json:"required_package"`
220 | Cards []uint `json:"cards"`
221 | Characters []uint `json:"characters"`
222 | }
223 | ```
224 |
225 | #### 响应示例
226 |
227 | ```json
228 | {
229 | "id": 3,
230 | "owner": 1,
231 | "required_package": [
232 | "enhance"
233 | ],
234 | "cards": [
235 | 1
236 | ],
237 | "characters": [
238 | 1
239 | ]
240 | }
241 | ```
242 |
243 |
244 |
245 | # 删除卡组
246 |
247 | ## 请求方法与地址
248 |
249 | ```plain text
250 | DELETE {BaseURL}/card_deck/:card_deck_id
251 | ```
252 |
253 | ## 请求/响应
254 |
255 |
256 |
257 |
258 |
259 | ### 请求
260 |
261 | 本接口不需要提供请求体
262 |
263 | #### 请求示例
264 |
265 | ```http
266 | DELETE /card_deck/:card_deck_id HTTP/1.1
267 | Host: {BaseURL}
268 | Cookie: gisb_token={gisb_token_value}; gisb_token_id={gisb_token_id_value}
269 | ```
270 |
271 |
272 |
273 | ### 响应
274 |
275 | 此接口将根据您给定的id尝试删除卡组
276 |
277 | #### 响应体定义
278 |
279 | 本接口没有响应体,返回HTTP Status 200即表明删除成功
280 |
281 |
--------------------------------------------------------------------------------
/mod/implement/util.go:
--------------------------------------------------------------------------------
1 | package implement
2 |
3 | import (
4 | "fmt"
5 | "github.com/sunist-c/genius-invokation-simulator-backend/mod/definition"
6 | "github.com/sunist-c/genius-invokation-simulator-backend/util"
7 | "gopkg.in/yaml.v2"
8 | "os"
9 | "os/user"
10 | "path"
11 | "time"
12 | )
13 |
14 | const (
15 | maxID uint16 = 1<<16 - 1
16 | )
17 |
18 | var (
19 | debugFlag = false
20 | metadata = ModInfo{
21 | ModID: 0,
22 | LastID: 0,
23 | UsedID: map[uint16]bool{},
24 | CreatedAt: time.Time{},
25 | UpdatedAt: time.Time{},
26 | Author: "",
27 | }
28 | metadataPath = ""
29 | )
30 |
31 | type ModInfo struct {
32 | ModID uint64 `yaml:"mod_id"`
33 | LastID uint16 `yaml:"last_id"`
34 | UsedID map[uint16]bool `yaml:"id_pool"`
35 | CreatedAt time.Time `yaml:"created_at"`
36 | UpdatedAt time.Time `yaml:"updated_at"`
37 | Author string `yaml:"author"`
38 | }
39 |
40 | func InitMetaData() {
41 | // 非debug模式,获取真实ModID
42 | if !debugFlag {
43 | // 尝试读取配置
44 | if pwd, err := os.Getwd(); err != nil {
45 | // 无法获取pwd,意味着无法写入也无法读取,报panic
46 | panic(fmt.Sprintf("cannot get current working directory: %v", err))
47 | } else if bytes, err := os.ReadFile(path.Join(pwd, "metadata.yml")); err == nil {
48 | metadataPath = path.Join(pwd, "metadata.yml")
49 |
50 | // 存在metadata.yml,尝试加载
51 | if yaml.Unmarshal(bytes, &metadata) != nil {
52 | // 反序列化metadata.yml失败,报panic
53 | panic(fmt.Sprintf("cannot unmarshal metadata.yml: %v", err))
54 | } else {
55 | // 反序列化metadata.yml成功,加载
56 | return
57 | }
58 | } else {
59 | // 不存在metadata.yml,生成
60 | metadataPath = path.Join(pwd, "metadata.yml")
61 |
62 | // 生成ModID
63 | if macAddresses, err := util.GetMacAddresses(); err != nil {
64 | // 无法获取mac地址,实际环境不存在这种情况,报panic
65 | panic(fmt.Sprintf("couldn't get mac addresses: %v", err))
66 | } else if len(macAddresses) == 0 {
67 | // mac地址不存在,实际环境不存在这种情况,报panic
68 | panic(fmt.Sprintf("couldn't get mac addresses: %v", err))
69 | } else if uintMacAddress, err := util.GetUintMacAddress(macAddresses[0]); err != nil {
70 | // mac地址不合法,实际环境不存在这种情况,报panic
71 | panic(fmt.Sprintf("couldn't get mac address: %v", err))
72 | } else {
73 | // 成功获取mac地址,作为设备码使用自定义雪花算法生成UID
74 | metadata.ModID = util.GenerateUID(uintMacAddress, time.Now())
75 | }
76 |
77 | // 生成Author
78 | if currentUser, err := user.Current(); err != nil {
79 | // 获取CurrentUser失败,设置为Unknown
80 | metadata.Author = "Unknown"
81 | } else {
82 | // 成功获取CurrentUser
83 | metadata.Author = currentUser.Name
84 | }
85 |
86 | // 生成metadata上下文
87 | metadata.LastID = 0
88 | metadata.UsedID = map[uint16]bool{}
89 |
90 | // 生成TimeStamp
91 | metadata.CreatedAt = time.Now()
92 |
93 | // 将生成的metadata写入metadata.yml
94 | if err := flushMetadata(); err != nil {
95 | // 写入metadata.yml失败,报panic
96 | panic(err)
97 | }
98 | }
99 | }
100 | }
101 |
102 | func flushMetadata() error {
103 | if !debugFlag {
104 | metadata.UpdatedAt = time.Now()
105 | if oStream, err := yaml.Marshal(metadata); err != nil {
106 | // 序列化metadata.yml失败,实际环境不存在这种情况
107 | return fmt.Errorf("marshal metadata.yml failed: %v", err)
108 | } else if file, err := os.OpenFile(metadataPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755); err != nil {
109 | // 无法打开metadata.yml,实际环境不存在这种情况
110 | return fmt.Errorf("cannot open metadata.yml: %v", err)
111 | } else if _, err := file.Write(oStream); err != nil {
112 | // 无法写入metadata.yml,实际环境不存在这种情况
113 | return fmt.Errorf("cannot write metadata.yml: %v", err)
114 | } else {
115 | // 成功写入metadata.yml
116 | return nil
117 | }
118 | } else {
119 | return nil
120 | }
121 | }
122 |
123 | func SetDebugFlag(flag bool) {
124 | debugFlag = flag
125 | }
126 |
127 | func ModID() uint64 {
128 | if debugFlag {
129 | return 109951162778600
130 | } else if metadata.ModID != 0 {
131 | return metadata.ModID
132 | } else {
133 | InitMetaData()
134 | return metadata.ModID
135 | }
136 | }
137 |
138 | // NextID 使用内置的ID分配工具获取一个可用的不与其余被托管ID冲突的自增ID
139 | func NextID() uint16 {
140 | defer func() {
141 | err := flushMetadata()
142 | if err != nil {
143 | panic(err)
144 | }
145 | }()
146 |
147 | if !metadata.UsedID[metadata.LastID+1] {
148 | metadata.LastID += 1
149 | metadata.UsedID[metadata.LastID] = true
150 | return metadata.LastID
151 | } else {
152 | for i := uint32(1); i <= uint32(maxID); i++ {
153 | if !metadata.UsedID[uint16(i)] {
154 | metadata.LastID = uint16(i)
155 | metadata.UsedID[uint16(i)] = true
156 | return uint16(i)
157 | }
158 | }
159 | }
160 |
161 | panic("entity id overflow")
162 | }
163 |
164 | // UseID 使用内置的ID分配工具分配一个指定的ID,若不可用,则分配一个自增的ID
165 | func UseID(want uint16) (success bool, result uint16) {
166 | defer func() {
167 | err := flushMetadata()
168 | if err != nil {
169 | panic(err)
170 | }
171 | }()
172 |
173 | if !metadata.UsedID[want] {
174 | // 成功分配want作为ID
175 | metadata.UsedID[want] = true
176 | return true, want
177 | } else {
178 | // 分配失败,从want开始查找下一个可用ID并分配
179 | for i := uint32(want); i <= uint32(maxID); i++ {
180 | if !metadata.UsedID[uint16(i)] {
181 | metadata.LastID = uint16(i)
182 | metadata.UsedID[uint16(i)] = true
183 | return false, uint16(i)
184 | }
185 | }
186 |
187 | // 分配失败,ID溢出,报panic
188 | panic("entity id overflow")
189 | }
190 | }
191 |
192 | func RegisterMod(mod definition.Mod) (success bool) {
193 | // todo: implement me
194 | panic("not implemented yet")
195 | }
196 |
--------------------------------------------------------------------------------