init
This commit is contained in:
commit
d787e2f431
|
@ -0,0 +1,2 @@
|
|||
extends:
|
||||
- "@commitlint/config-conventional"
|
|
@ -0,0 +1,19 @@
|
|||
.git
|
||||
.env*
|
||||
README.md
|
||||
LICENSE
|
||||
renovate.json
|
||||
.gitignore
|
||||
.dockerignore
|
||||
bin
|
||||
.golangci.yml
|
||||
.pre-commit-config.yaml
|
||||
build
|
||||
k8s
|
||||
.commitlintrc.yml
|
||||
additional_envs.sh
|
||||
skaffold.yml
|
||||
.editorconfig
|
||||
.woodpecker
|
||||
.kpt-pipeline
|
||||
tmp
|
|
@ -0,0 +1,5 @@
|
|||
PATH_add bin
|
||||
export GOBIN=$PWD/bin
|
||||
if [ -f "additional_envs.sh" ]; then
|
||||
source <(cat additional_envs.sh)
|
||||
fi
|
|
@ -0,0 +1,5 @@
|
|||
.idea
|
||||
bin/*
|
||||
additional_envs.sh
|
||||
.kpt-pipeline
|
||||
tmp
|
|
@ -0,0 +1,476 @@
|
|||
run:
|
||||
tests: true
|
||||
timeout: 5m
|
||||
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- gocyclo
|
||||
- asasalint
|
||||
- asciicheck
|
||||
- bodyclose
|
||||
- bidichk
|
||||
- exportloopref
|
||||
- errcheck
|
||||
- gocritic
|
||||
- gosec
|
||||
- gofmt
|
||||
- goimports
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- misspell
|
||||
- nakedret
|
||||
- prealloc
|
||||
- staticcheck
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unused
|
||||
- lll
|
||||
- nestif
|
||||
- thelper
|
||||
- nonamedreturns
|
||||
- tenv
|
||||
- testpackage
|
||||
- noctx
|
||||
- tparallel
|
||||
- usestdlibvars
|
||||
- unconvert
|
||||
- makezero
|
||||
- grouper
|
||||
- errname
|
||||
- exhaustive
|
||||
- tagliatelle
|
||||
- contextcheck
|
||||
- gocheckcompilerdirectives
|
||||
- errname
|
||||
- forcetypeassert
|
||||
- durationcheck
|
||||
- predeclared
|
||||
- promlinter
|
||||
- wastedassign
|
||||
- testifylint
|
||||
- inamedparam
|
||||
- sloglint
|
||||
- revive
|
||||
|
||||
linters-settings:
|
||||
gocyclo:
|
||||
min-complexity: 10
|
||||
tagliatelle:
|
||||
case:
|
||||
rules:
|
||||
json: camel
|
||||
bun: snake
|
||||
lll:
|
||||
line-length: 120
|
||||
govet:
|
||||
enable:
|
||||
- asmdecl
|
||||
- assign
|
||||
- atomic
|
||||
- atomicalign
|
||||
- bools
|
||||
- buildtag
|
||||
- cgocall
|
||||
- composites
|
||||
- copylocks
|
||||
- deepequalerrors
|
||||
- errorsas
|
||||
- findcall
|
||||
- framepointer
|
||||
- httpresponse
|
||||
- ifaceassert
|
||||
- loopclosure
|
||||
- lostcancel
|
||||
- nilfunc
|
||||
- nilness
|
||||
- printf
|
||||
- reflectvaluecompare
|
||||
- shadow
|
||||
- shift
|
||||
- sigchanyzer
|
||||
- sortslice
|
||||
- stdmethods
|
||||
- stringintconv
|
||||
- structtag
|
||||
- testinggoroutine
|
||||
- tests
|
||||
- unmarshal
|
||||
- unreachable
|
||||
- unsafeptr
|
||||
- unusedresult
|
||||
- unusedwrite
|
||||
testifylint:
|
||||
enable-all: true
|
||||
sloglint:
|
||||
attr-only: true
|
||||
revive:
|
||||
rules:
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#add-constant
|
||||
- name: add-constant
|
||||
severity: warning
|
||||
disabled: true
|
||||
arguments:
|
||||
- maxLitCount: "3"
|
||||
allowStrs: "\"\""
|
||||
ignoreFuncs: os.Exit,wg.Add,make
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#argument-limit
|
||||
- name: argument-limit
|
||||
severity: warning
|
||||
disabled: true
|
||||
arguments: [4]
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#atomic
|
||||
- name: atomic
|
||||
severity: error
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#banned-characters
|
||||
- name: banned-characters
|
||||
severity: warning
|
||||
disabled: true
|
||||
arguments: []
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#bare-return
|
||||
- name: bare-return
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#blank-imports
|
||||
- name: blank-imports
|
||||
severity: error
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#bool-literal-in-expr
|
||||
- name: bool-literal-in-expr
|
||||
severity: error
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#call-to-gc
|
||||
- name: call-to-gc
|
||||
severity: error
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#cognitive-complexity
|
||||
- name: cognitive-complexity
|
||||
severity: warning
|
||||
disabled: true
|
||||
arguments: [7]
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#comment-spacings
|
||||
- name: comment-spacings
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#confusing-naming
|
||||
- name: confusing-naming
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#confusing-results
|
||||
- name: confusing-results
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#constant-logical-expr
|
||||
- name: constant-logical-expr
|
||||
severity: error
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#context-as-argument
|
||||
- name: context-as-argument
|
||||
severity: error
|
||||
disabled: false
|
||||
arguments:
|
||||
- allowTypesBefore: "*testing.T,testing.TB"
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#context-keys-type
|
||||
- name: context-keys-type
|
||||
severity: error
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#cyclomatic
|
||||
- name: cyclomatic
|
||||
severity: warning
|
||||
disabled: true
|
||||
arguments: [10]
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#datarace
|
||||
- name: datarace
|
||||
severity: error
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#deep-exit
|
||||
- name: deep-exit
|
||||
severity: error
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#defer
|
||||
- name: defer
|
||||
severity: error
|
||||
disabled: false
|
||||
arguments:
|
||||
- [call-chain, loop]
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#dot-imports
|
||||
- name: dot-imports
|
||||
severity: error
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#duplicated-imports
|
||||
- name: duplicated-imports
|
||||
severity: error
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#early-return
|
||||
- name: early-return
|
||||
severity: error
|
||||
disabled: false
|
||||
arguments:
|
||||
- preserveScope
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#empty-block
|
||||
- name: empty-block
|
||||
severity: error
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#empty-lines
|
||||
- name: empty-lines
|
||||
severity: error
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#enforce-map-style
|
||||
- name: enforce-map-style
|
||||
severity: error
|
||||
disabled: false
|
||||
arguments:
|
||||
- make
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#error-naming
|
||||
- name: error-naming
|
||||
severity: error
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#error-return
|
||||
- name: error-return
|
||||
severity: error
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#error-strings
|
||||
- name: error-strings
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#errorf
|
||||
- name: errorf
|
||||
severity: error
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#exported
|
||||
- name: exported
|
||||
severity: warning
|
||||
disabled: true
|
||||
arguments:
|
||||
- preserveScope
|
||||
- checkPrivateReceivers
|
||||
- sayRepetitiveInsteadOfStutters
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#file-header
|
||||
- name: file-header
|
||||
severity: warning
|
||||
disabled: true
|
||||
arguments:
|
||||
- This is the text that must appear at the top of source files.
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#flag-parameter
|
||||
- name: flag-parameter
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#function-result-limit
|
||||
- name: function-result-limit
|
||||
severity: warning
|
||||
disabled: false
|
||||
arguments: [4]
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#function-length
|
||||
- name: function-length
|
||||
severity: warning
|
||||
disabled: true
|
||||
arguments: [10, 0]
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#get-return
|
||||
- name: get-return
|
||||
severity: warning
|
||||
disabled: true
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#identical-branches
|
||||
- name: identical-branches
|
||||
severity: error
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#if-return
|
||||
- name: if-return
|
||||
severity: error
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#increment-decrement
|
||||
- name: increment-decrement
|
||||
severity: error
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#indent-error-flow
|
||||
- name: indent-error-flow
|
||||
severity: warning
|
||||
disabled: false
|
||||
arguments:
|
||||
- preserveScope
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#import-alias-naming
|
||||
- name: import-alias-naming
|
||||
severity: warning
|
||||
disabled: false
|
||||
arguments:
|
||||
- ^[a-z][a-z0-9]{0,}$
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#imports-blacklist
|
||||
- name: imports-blacklist
|
||||
severity: error
|
||||
disabled: false
|
||||
arguments:
|
||||
- reflect
|
||||
- github.com/pkg/errors
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#import-shadowing
|
||||
- name: import-shadowing
|
||||
severity: error
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#line-length-limit
|
||||
- name: line-length-limit
|
||||
severity: warning
|
||||
# lll is enabled
|
||||
disabled: true
|
||||
arguments: [80]
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#max-public-structs
|
||||
- name: max-public-structs
|
||||
severity: warning
|
||||
disabled: true
|
||||
arguments: [3]
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#modifies-parameter
|
||||
- name: modifies-parameter
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#modifies-value-receiver
|
||||
- name: modifies-value-receiver
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#nested-structs
|
||||
- name: nested-structs
|
||||
severity: error
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#optimize-operands-order
|
||||
- name: optimize-operands-order
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#package-comments
|
||||
- name: package-comments
|
||||
severity: warning
|
||||
disabled: true
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#range
|
||||
- name: range
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#range-val-in-closure
|
||||
- name: range-val-in-closure
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#range-val-address
|
||||
- name: range-val-address
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#receiver-naming
|
||||
- name: receiver-naming
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#redundant-import-alias
|
||||
- name: redundant-import-alias
|
||||
severity: error
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#redefines-builtin-id
|
||||
- name: redefines-builtin-id
|
||||
severity: error
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#string-of-int
|
||||
- name: string-of-int
|
||||
severity: warning
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#string-format
|
||||
- name: string-format
|
||||
severity: warning
|
||||
disabled: true
|
||||
arguments:
|
||||
- - core.WriteError[1].Message
|
||||
- /^([^A-Z]|$)/
|
||||
- must not start with a capital letter
|
||||
- - fmt.Errorf[0]
|
||||
- /(^|[^\.!?])$/
|
||||
- must not end in punctuation
|
||||
- - panic
|
||||
- /^[^\n]*$/
|
||||
- must not contain line breaks
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#struct-tag
|
||||
- name: struct-tag
|
||||
arguments:
|
||||
- json,inline
|
||||
- bson,outline,gnu
|
||||
severity: warning
|
||||
# tagliatelle is enabled
|
||||
disabled: true
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#superfluous-else
|
||||
- name: superfluous-else
|
||||
severity: error
|
||||
disabled: false
|
||||
arguments:
|
||||
- preserveScope
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#time-equal
|
||||
- name: time-equal
|
||||
severity: error
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#time-naming
|
||||
- name: time-naming
|
||||
severity: error
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#var-naming
|
||||
- name: var-naming
|
||||
severity: error
|
||||
disabled: false
|
||||
arguments:
|
||||
- [] # AllowList
|
||||
- [] # DenyList
|
||||
- - upperCaseConst: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#var-declaration
|
||||
- name: var-declaration
|
||||
severity: error
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unconditional-recursion
|
||||
- name: unconditional-recursion
|
||||
severity: error
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unexported-naming
|
||||
- name: unexported-naming
|
||||
severity: error
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unexported-return
|
||||
- name: unexported-return
|
||||
severity: error
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unhandled-error
|
||||
- name: unhandled-error
|
||||
severity: warning
|
||||
disabled: false
|
||||
arguments:
|
||||
- fmt.Printf
|
||||
- fmt.Println
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unnecessary-stmt
|
||||
- name: unnecessary-stmt
|
||||
severity: error
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unreachable-code
|
||||
- name: unreachable-code
|
||||
severity: error
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unused-parameter
|
||||
- name: unused-parameter
|
||||
severity: error
|
||||
disabled: false
|
||||
arguments:
|
||||
- allowRegex: ^_
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unused-receiver
|
||||
- name: unused-receiver
|
||||
severity: error
|
||||
disabled: true
|
||||
arguments:
|
||||
- allowRegex: ^_
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#useless-break
|
||||
- name: useless-break
|
||||
severity: error
|
||||
disabled: false
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#waitgroup-by-value
|
||||
- name: waitgroup-by-value
|
||||
severity: warning
|
||||
disabled: false
|
||||
|
||||
|
||||
issues:
|
||||
exclude-rules:
|
||||
# Exclude some linters from running on tests files.
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- gosec
|
||||
- gocyclo
|
||||
- path: _test\.go
|
||||
text: add-constant
|
||||
- linters:
|
||||
- lll
|
||||
source: "^//go:generate "
|
|
@ -0,0 +1,2 @@
|
|||
failure-threshold: error
|
||||
format: tty
|
|
@ -0,0 +1,20 @@
|
|||
repos:
|
||||
- repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook
|
||||
rev: v9.10.0
|
||||
hooks:
|
||||
- id: commitlint
|
||||
stages: [commit-msg]
|
||||
additional_dependencies: ["@commitlint/config-conventional"]
|
||||
- repo: https://github.com/golangci/golangci-lint
|
||||
rev: v1.55.2
|
||||
hooks:
|
||||
- id: golangci-lint
|
||||
- repo: https://github.com/hadolint/hadolint
|
||||
rev: v2.12.0
|
||||
hooks:
|
||||
- id: hadolint
|
||||
- repo: https://github.com/adrienverge/yamllint.git
|
||||
rev: v1.33.0
|
||||
hooks:
|
||||
- id: yamllint
|
||||
args: [--strict, -c=./.yamllint.yml]
|
|
@ -0,0 +1,41 @@
|
|||
when:
|
||||
- event: [cron]
|
||||
cron: govulncheck
|
||||
- event: [pull_request]
|
||||
- event: push
|
||||
branch:
|
||||
- ${CI_REPO_DEFAULT_BRANCH}
|
||||
|
||||
variables:
|
||||
- &go_image 'golang:1.21'
|
||||
|
||||
steps:
|
||||
govulncheck:
|
||||
image: *go_image
|
||||
pull: true
|
||||
commands:
|
||||
- make generate
|
||||
- go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||
- govulncheck ./...
|
||||
|
||||
notify:
|
||||
image: deblan/woodpecker-email
|
||||
settings:
|
||||
from:
|
||||
from_secret: email_from
|
||||
from.name: Woodpecker
|
||||
host:
|
||||
from_secret: email_host
|
||||
username:
|
||||
from_secret: email_username
|
||||
password:
|
||||
from_secret: email_password
|
||||
recipients:
|
||||
- notifications@dwysokinski.me
|
||||
recipients_only: true
|
||||
subject:
|
||||
"[govulncheck - {{ build.status }}] {{ repo.owner }}/{{ repo.name }}
|
||||
({{ build.branch }} - {{ truncate build.commit 8 }})"
|
||||
when:
|
||||
status: [success, failure]
|
||||
event: cron
|
|
@ -0,0 +1,71 @@
|
|||
when:
|
||||
- event: [pull_request]
|
||||
- event: push
|
||||
branch:
|
||||
- ${CI_REPO_DEFAULT_BRANCH}
|
||||
|
||||
services:
|
||||
database:
|
||||
image: postgres:14
|
||||
pull: true
|
||||
environment:
|
||||
POSTGRES_DB: twhelp
|
||||
POSTGRES_PASSWORD: twhelp
|
||||
|
||||
variables:
|
||||
- &go_image 'golang:1.21'
|
||||
|
||||
steps:
|
||||
generate:
|
||||
image: *go_image
|
||||
pull: true
|
||||
commands:
|
||||
- go mod download
|
||||
- make generate
|
||||
|
||||
test:
|
||||
image: *go_image
|
||||
group: test
|
||||
pull: true
|
||||
environment:
|
||||
TESTS_POSTGRES_CONNECTION_STRING:
|
||||
postgres://postgres:twhelp@database:5432/twhelp?sslmode=disable
|
||||
commands:
|
||||
- go test -race -coverprofile=coverage.txt -covermode=atomic ./...
|
||||
|
||||
lint:
|
||||
image: golangci/golangci-lint:v1.55
|
||||
pull: true
|
||||
group: test
|
||||
commands:
|
||||
- golangci-lint run
|
||||
|
||||
check-go-mod:
|
||||
image: *go_image
|
||||
group: test
|
||||
pull: true
|
||||
commands:
|
||||
- go mod tidy
|
||||
- git diff --exit-code go.mod
|
||||
|
||||
hadolint:
|
||||
group: test
|
||||
image: hadolint/hadolint:2.12.0-debian
|
||||
commands:
|
||||
- hadolint build/docker/twhelp/prod/Dockerfile build/docker/twhelp/dev/Dockerfile
|
||||
when:
|
||||
- path:
|
||||
- build/docker/twhelp/prod/Dockerfile
|
||||
- build/docker/twhelp/dev/Dockerfile
|
||||
|
||||
yamllint:
|
||||
group: test
|
||||
image: cytopia/yamllint:1
|
||||
pull: true
|
||||
commands:
|
||||
- yamllint --strict .
|
||||
when:
|
||||
- path:
|
||||
include:
|
||||
- "**/*.yaml"
|
||||
- "**/*.yml"
|
|
@ -0,0 +1,15 @@
|
|||
extends: default
|
||||
|
||||
rules:
|
||||
line-length:
|
||||
max: 120
|
||||
level: error
|
||||
document-start: disable
|
||||
truthy:
|
||||
level: error
|
||||
comments:
|
||||
min-spaces-from-content: 1
|
||||
quoted-strings:
|
||||
level: warning
|
||||
required: only-when-needed
|
||||
quote-type: double
|
|
@ -0,0 +1,30 @@
|
|||
GOOS=$(shell go env GOOS)
|
||||
GOARCH=$(shell go env GOARCH)
|
||||
GOBIN=$(shell go env GOBIN)
|
||||
ifeq ($(GOBIN),)
|
||||
GOBIN := $(shell go env GOPATH)/bin
|
||||
endif
|
||||
OSARCH=$(shell uname -m)
|
||||
GOLANGCI_LINT_PATH=$(GOBIN)/golangci-lint
|
||||
|
||||
.PHONY: install-git-hooks
|
||||
install-git-hooks:
|
||||
@echo "Installing git hooks..."
|
||||
pre-commit install --hook-type pre-commit
|
||||
pre-commit install --hook-type commit-msg
|
||||
|
||||
.PHONY: install-golangci-lint
|
||||
install-golangci-lint:
|
||||
@echo "Installing github.com/golangci/golangci-lint..."
|
||||
@(test -f $(GOLANGCI_LINT_PATH) && echo "github.com/golangci/golangci-lint is already installed. Skipping...") || curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOBIN) v1.55.2
|
||||
|
||||
.PHONY: install-tools
|
||||
install-tools: install-golangci-lint
|
||||
|
||||
.PHONY: install
|
||||
install: install-tools install-git-hooks
|
||||
|
||||
.PHONY: generate
|
||||
generate:
|
||||
@echo "Running go generate..."
|
||||
go generate ./...
|
|
@ -0,0 +1,17 @@
|
|||
FROM golang:1.21 as builder
|
||||
WORKDIR /app
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
COPY . .
|
||||
# `skaffold debug` sets SKAFFOLD_GO_GCFLAGS to disable compiler optimizations
|
||||
ARG SKAFFOLD_GO_GCFLAGS
|
||||
RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o twhelp ./cmd/twhelp
|
||||
|
||||
FROM ubuntu:22.04
|
||||
# Define GOTRACEBACK to mark this container as using the Go language runtime
|
||||
# for `skaffold debug` (https://skaffold.dev/docs/workflows/debug/).
|
||||
WORKDIR /root
|
||||
ENV GOTRACEBACK=single
|
||||
RUN apt update && apt install -y ca-certificates tzdata
|
||||
COPY --from=builder /app/twhelp .
|
||||
ENTRYPOINT ["./twhelp"]
|
|
@ -0,0 +1,22 @@
|
|||
FROM golang:1.21.5-alpine3.19 AS builder
|
||||
|
||||
WORKDIR /twhelp
|
||||
|
||||
COPY ../../../../go.mod go.sum ./
|
||||
RUN go mod download && apk --no-cache add make
|
||||
|
||||
COPY ../../../.. .
|
||||
RUN make generate
|
||||
ARG CI_COMMIT_TAG="development"
|
||||
RUN CGO_ENABLED=0 go build -ldflags "-X main.version=${CI_COMMIT_TAG##v}" -trimpath -o twhelp ./cmd/twhelp
|
||||
|
||||
######## Start a new stage from scratch #######
|
||||
FROM alpine:3.19
|
||||
|
||||
RUN apk --no-cache add ca-certificates tzdata
|
||||
|
||||
COPY --from=builder /twhelp/twhelp /usr/bin/
|
||||
|
||||
EXPOSE 9234/tcp
|
||||
|
||||
ENTRYPOINT ["twhelp"]
|
|
@ -0,0 +1,14 @@
|
|||
module gitea.dwysokinski.me/twhelp/corev3
|
||||
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/elliotchance/phpserialize v1.3.3
|
||||
github.com/stretchr/testify v1.8.4
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
|
@ -0,0 +1,12 @@
|
|||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/elliotchance/phpserialize v1.3.3 h1:hV4QVmGdCiYgoBbw+ADt6fNgyZ2mYX0OgpnON1adTCM=
|
||||
github.com/elliotchance/phpserialize v1.3.3/go.mod h1:gt7XX9+ETUcLXbtTKEuyrqW3lcLUAeS/AnGZ2e49TZs=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
@ -0,0 +1,42 @@
|
|||
package tw
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
)
|
||||
|
||||
type Building struct {
|
||||
MaxLevel int `xml:"max_level"`
|
||||
MinLevel int `xml:"min_level"`
|
||||
Wood int `xml:"wood"`
|
||||
Stone int `xml:"stone"`
|
||||
Iron int `xml:"iron"`
|
||||
Pop int `xml:"pop"`
|
||||
WoodFactor float64 `xml:"wood_factor"`
|
||||
StoneFactor float64 `xml:"stone_factor"`
|
||||
IronFactor float64 `xml:"iron_factor"`
|
||||
PopFactor float64 `xml:"pop_factor"`
|
||||
BuildTime float64 `xml:"build_time"`
|
||||
BuildTimeFactor float64 `xml:"build_time_factor"`
|
||||
}
|
||||
|
||||
type BuildingInfo struct {
|
||||
XMLName xml.Name `xml:"config"`
|
||||
Text string `xml:",chardata"`
|
||||
Main Building `xml:"main"`
|
||||
Barracks Building `xml:"barracks"`
|
||||
Stable Building `xml:"stable"`
|
||||
Garage Building `xml:"garage"`
|
||||
Watchtower Building `xml:"watchtower"`
|
||||
Snob Building `xml:"snob"`
|
||||
Smith Building `xml:"smith"`
|
||||
Place Building `xml:"place"`
|
||||
Statue Building `xml:"statue"`
|
||||
Market Building `xml:"market"`
|
||||
Wood Building `xml:"wood"`
|
||||
Stone Building `xml:"stone"`
|
||||
Iron Building `xml:"iron"`
|
||||
Farm Building `xml:"farm"`
|
||||
Storage Building `xml:"storage"`
|
||||
Hide Building `xml:"hide"`
|
||||
Wall Building `xml:"wall"`
|
||||
}
|
|
@ -0,0 +1,804 @@
|
|||
package tw
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"encoding/csv"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"slices"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/elliotchance/phpserialize"
|
||||
)
|
||||
|
||||
const (
|
||||
endpointPlayers = "/map/player.txt"
|
||||
endpointTribes = "/map/ally.txt"
|
||||
endpointVillages = "/map/village.txt"
|
||||
endpointPlayersODA = "/map/kill_att.txt"
|
||||
endpointPlayersODD = "/map/kill_def.txt"
|
||||
endpointPlayersODS = "/map/kill_sup.txt"
|
||||
endpointPlayersOD = "/map/kill_all.txt"
|
||||
endpointTribesODA = "/map/kill_att_tribe.txt"
|
||||
endpointTribesODD = "/map/kill_def_tribe.txt"
|
||||
endpointTribesOD = "/map/kill_all_tribe.txt"
|
||||
endpointEnnoblements = "/map/conquer_extended.txt"
|
||||
endpointInterface = "/interface.php"
|
||||
endpointGetServers = "/backend/get_servers.php"
|
||||
endpointGame = "/game.php"
|
||||
)
|
||||
|
||||
const (
|
||||
queryConfig = "func=get_config"
|
||||
queryUnitInfo = "func=get_unit_info"
|
||||
queryBuildingInfo = "func=get_building_info"
|
||||
queryInterfaceEnnoblements = "func=get_conquer_extended&since=%d"
|
||||
queryPlayerProfile = "screen=info_player&id=%d"
|
||||
queryTribeProfile = "screen=info_ally&id=%d"
|
||||
queryVillageProfile = "screen=info_village&id=%d"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
client *http.Client
|
||||
userAgent string
|
||||
ennoblementsUseInterfaceFunc func(since time.Time) bool
|
||||
}
|
||||
|
||||
func NewClient(opts ...ClientOption) *Client {
|
||||
cfg := newClientConfig(opts...)
|
||||
return &Client{
|
||||
client: cfg.client,
|
||||
userAgent: cfg.userAgent,
|
||||
ennoblementsUseInterfaceFunc: cfg.ennoblementsUseInterfaceFunc,
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidServerKey = errors.New("invalid server key")
|
||||
ErrInvalidServerURL = errors.New("invalid server URL")
|
||||
)
|
||||
|
||||
func (c *Client) GetOpenServers(ctx context.Context, rawBaseURL string) ([]Server, error) {
|
||||
baseURL, err := url.ParseRequestURI(rawBaseURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
u := buildURL(baseURL, endpointGetServers)
|
||||
resp, err := c.get(ctx, u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: couldn't read response body: %w", u, err)
|
||||
}
|
||||
|
||||
m, err := phpserialize.UnmarshalAssociativeArray(b)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: couldn't unmarshal response body: %w", u, err)
|
||||
}
|
||||
|
||||
servers := make([]Server, 0, len(m))
|
||||
for key, val := range m {
|
||||
keyStr, ok := key.(string)
|
||||
if !ok || keyStr == "" {
|
||||
return nil, fmt.Errorf("%s: parsing '%v': %w", u, key, ErrInvalidServerKey)
|
||||
}
|
||||
|
||||
urlStr, ok := val.(string)
|
||||
if !ok || urlStr == "" {
|
||||
return nil, fmt.Errorf("%s: parsing '%v': %w", u, val, ErrInvalidServerURL)
|
||||
}
|
||||
|
||||
servers = append(servers, Server{
|
||||
Key: keyStr,
|
||||
URL: urlStr,
|
||||
})
|
||||
}
|
||||
|
||||
return servers, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetServerConfig(ctx context.Context, rawBaseURL string) (ServerConfig, error) {
|
||||
baseURL, err := url.ParseRequestURI(rawBaseURL)
|
||||
if err != nil {
|
||||
return ServerConfig{}, err
|
||||
}
|
||||
|
||||
var cfg ServerConfig
|
||||
|
||||
if err = c.getXML(ctx, buildURLWithQuery(baseURL, endpointInterface, queryConfig), &cfg); err != nil {
|
||||
return ServerConfig{}, err
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetBuildingInfo(ctx context.Context, rawBaseURL string) (BuildingInfo, error) {
|
||||
baseURL, err := url.ParseRequestURI(rawBaseURL)
|
||||
if err != nil {
|
||||
return BuildingInfo{}, err
|
||||
}
|
||||
|
||||
var info BuildingInfo
|
||||
|
||||
if err = c.getXML(ctx, buildURLWithQuery(baseURL, endpointInterface, queryBuildingInfo), &info); err != nil {
|
||||
return BuildingInfo{}, err
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetUnitInfo(ctx context.Context, rawBaseURL string) (UnitInfo, error) {
|
||||
baseURL, err := url.ParseRequestURI(rawBaseURL)
|
||||
if err != nil {
|
||||
return UnitInfo{}, err
|
||||
}
|
||||
|
||||
var info UnitInfo
|
||||
|
||||
if err = c.getXML(ctx, buildURLWithQuery(baseURL, endpointInterface, queryUnitInfo), &info); err != nil {
|
||||
return UnitInfo{}, err
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetTribes(ctx context.Context, rawBaseURL string) ([]Tribe, error) {
|
||||
baseURL, err := url.ParseRequestURI(rawBaseURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
od, err := c.getOD(ctx, baseURL, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := c.get(ctx, buildURL(baseURL, endpointTribes))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
return tribeCSVParser{
|
||||
r: resp.Body,
|
||||
baseURL: baseURL,
|
||||
od: od,
|
||||
}.parse()
|
||||
}
|
||||
|
||||
func (c *Client) GetPlayers(ctx context.Context, rawBaseURL string) ([]Player, error) {
|
||||
baseURL, err := url.ParseRequestURI(rawBaseURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
od, err := c.getOD(ctx, baseURL, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := c.get(ctx, buildURL(baseURL, endpointPlayers))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
return playerCSVParser{
|
||||
r: resp.Body,
|
||||
baseURL: baseURL,
|
||||
od: od,
|
||||
}.parse()
|
||||
}
|
||||
|
||||
func (c *Client) getOD(ctx context.Context, baseURL *url.URL, tribe bool) (map[int]OpponentsDefeated, error) {
|
||||
m := make(map[int]OpponentsDefeated)
|
||||
urls := buildODURLs(baseURL, tribe)
|
||||
|
||||
for _, u := range urls {
|
||||
if u == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
records, err := c.getSingleODFile(ctx, u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, rec := range records {
|
||||
od := m[rec.ID]
|
||||
|
||||
switch u {
|
||||
case urls[0]:
|
||||
od.RankTotal = rec.Rank
|
||||
od.ScoreTotal = rec.Score
|
||||
case urls[1]:
|
||||
od.RankAtt = rec.Rank
|
||||
od.ScoreAtt = rec.Score
|
||||
case urls[2]:
|
||||
od.RankDef = rec.Rank
|
||||
od.ScoreDef = rec.Score
|
||||
case urls[3]:
|
||||
od.RankSup = rec.Rank
|
||||
od.ScoreSup = rec.Score
|
||||
}
|
||||
|
||||
m[rec.ID] = od
|
||||
}
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (c *Client) getSingleODFile(ctx context.Context, u *url.URL) ([]odRecord, error) {
|
||||
resp, err := c.get(ctx, u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
return odCSVParser{
|
||||
r: resp.Body,
|
||||
}.parse()
|
||||
}
|
||||
|
||||
func (c *Client) GetVillages(ctx context.Context, rawBaseURL string) ([]Village, error) {
|
||||
baseURL, err := url.ParseRequestURI(rawBaseURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := c.get(ctx, buildURL(baseURL, endpointVillages))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
return villageCSVParser{
|
||||
r: resp.Body,
|
||||
baseURL: baseURL,
|
||||
}.parse()
|
||||
}
|
||||
|
||||
func (c *Client) GetEnnoblements(ctx context.Context, rawBaseURL string, since time.Time) ([]Ennoblement, error) {
|
||||
baseURL, err := url.ParseRequestURI(rawBaseURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
u := buildURL(baseURL, endpointEnnoblements)
|
||||
if c.ennoblementsUseInterfaceFunc(since) {
|
||||
u = buildURLWithQuery(baseURL, endpointInterface, fmt.Sprintf(queryInterfaceEnnoblements, since.Unix()))
|
||||
}
|
||||
|
||||
resp, err := c.get(ctx, u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
return ennoblementCSVParser{
|
||||
r: resp.Body,
|
||||
since: since,
|
||||
}.parse()
|
||||
}
|
||||
|
||||
func (c *Client) getXML(ctx context.Context, u *url.URL, v any) error {
|
||||
resp, err := c.get(ctx, u)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_, _ = io.Copy(io.Discard, resp.Body)
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
if err = xml.NewDecoder(resp.Body).Decode(v); err != nil {
|
||||
return fmt.Errorf("%s: %w", u, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) get(ctx context.Context, u *url.URL) (*http.Response, error) {
|
||||
urlStr := u.String()
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, urlStr, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", urlStr, err)
|
||||
}
|
||||
|
||||
// headers
|
||||
req.Header.Set("User-Agent", c.userAgent)
|
||||
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", urlStr, err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
_, _ = io.Copy(io.Discard, resp.Body)
|
||||
_ = resp.Body.Close()
|
||||
return nil, fmt.Errorf("%s: got non-ok HTTP status: %d", urlStr, resp.StatusCode)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
type tribeCSVParser struct {
|
||||
r io.Reader
|
||||
baseURL *url.URL
|
||||
od map[int]OpponentsDefeated
|
||||
}
|
||||
|
||||
const fieldsPerRecordTribe = 8
|
||||
|
||||
func (t tribeCSVParser) parse() ([]Tribe, error) {
|
||||
csvR := newCSVReader(t.r, fieldsPerRecordTribe)
|
||||
|
||||
var tribes []Tribe
|
||||
for {
|
||||
rec, err := csvR.Read()
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tribe, err := t.parseRecord(rec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tribes = append(tribes, tribe)
|
||||
}
|
||||
|
||||
slices.SortFunc(tribes, func(a, b Tribe) int {
|
||||
return cmp.Compare(a.ID, b.ID)
|
||||
})
|
||||
|
||||
return tribes, nil
|
||||
}
|
||||
|
||||
func (t tribeCSVParser) parseRecord(record []string) (Tribe, error) {
|
||||
var err error
|
||||
var tribe Tribe
|
||||
|
||||
// $id, $name, $tag, $members, $villages, $points, $all_points, $rank
|
||||
|
||||
tribe.ID, err = strconv.Atoi(record[0])
|
||||
if err != nil {
|
||||
return Tribe{}, ParseError{Err: err, Str: record[0], Field: "Tribe.ID"}
|
||||
}
|
||||
|
||||
tribe.Name, err = url.QueryUnescape(record[1])
|
||||
if err != nil {
|
||||
return Tribe{}, ParseError{Err: err, Str: record[1], Field: "Tribe.Name"}
|
||||
}
|
||||
|
||||
tribe.Tag, err = url.QueryUnescape(record[2])
|
||||
if err != nil {
|
||||
return Tribe{}, ParseError{Err: err, Str: record[2], Field: "Tribe.Tag"}
|
||||
}
|
||||
|
||||
tribe.NumMembers, err = strconv.Atoi(record[3])
|
||||
if err != nil {
|
||||
return Tribe{}, ParseError{Err: err, Str: record[3], Field: "Tribe.NumMembers"}
|
||||
}
|
||||
|
||||
tribe.NumVillages, err = strconv.Atoi(record[4])
|
||||
if err != nil {
|
||||
return Tribe{}, ParseError{Err: err, Str: record[4], Field: "Tribe.NumVillages"}
|
||||
}
|
||||
|
||||
tribe.Points, err = strconv.Atoi(record[5])
|
||||
if err != nil {
|
||||
return Tribe{}, ParseError{Err: err, Str: record[5], Field: "Tribe.Points"}
|
||||
}
|
||||
|
||||
tribe.AllPoints, err = strconv.Atoi(record[6])
|
||||
if err != nil {
|
||||
return Tribe{}, ParseError{Err: err, Str: record[6], Field: "Tribe.AllPoints"}
|
||||
}
|
||||
|
||||
tribe.Rank, err = strconv.Atoi(record[7])
|
||||
if err != nil {
|
||||
return Tribe{}, ParseError{Err: err, Str: record[7], Field: "Tribe.Rank"}
|
||||
}
|
||||
|
||||
tribe.OpponentsDefeated = t.od[tribe.ID]
|
||||
|
||||
tribe.ProfileURL = buildURLWithQuery(t.baseURL, endpointGame, fmt.Sprintf(queryTribeProfile, tribe.ID)).String()
|
||||
|
||||
return tribe, nil
|
||||
}
|
||||
|
||||
type playerCSVParser struct {
|
||||
r io.Reader
|
||||
od map[int]OpponentsDefeated
|
||||
baseURL *url.URL
|
||||
}
|
||||
|
||||
const fieldsPerRecordPlayer = 6
|
||||
|
||||
func (p playerCSVParser) parse() ([]Player, error) {
|
||||
csvR := newCSVReader(p.r, fieldsPerRecordPlayer)
|
||||
|
||||
var players []Player
|
||||
for {
|
||||
rec, err := csvR.Read()
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
player, err := p.parseRecord(rec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
players = append(players, player)
|
||||
}
|
||||
|
||||
slices.SortFunc(players, func(a, b Player) int {
|
||||
return cmp.Compare(a.ID, b.ID)
|
||||
})
|
||||
|
||||
return players, nil
|
||||
}
|
||||
|
||||
func (p playerCSVParser) parseRecord(record []string) (Player, error) {
|
||||
var err error
|
||||
var player Player
|
||||
|
||||
// $id, $name, $ally, $villages, $points, $rank
|
||||
|
||||
player.ID, err = strconv.Atoi(record[0])
|
||||
if err != nil {
|
||||
return Player{}, ParseError{Err: err, Str: record[0], Field: "Player.ID"}
|
||||
}
|
||||
|
||||
player.Name, err = url.QueryUnescape(record[1])
|
||||
if err != nil {
|
||||
return Player{}, ParseError{Err: err, Str: record[1], Field: "Player.Name"}
|
||||
}
|
||||
|
||||
player.TribeID, err = strconv.Atoi(record[2])
|
||||
if err != nil {
|
||||
return Player{}, ParseError{Err: err, Str: record[2], Field: "Player.TribeID"}
|
||||
}
|
||||
|
||||
player.NumVillages, err = strconv.Atoi(record[3])
|
||||
if err != nil {
|
||||
return Player{}, ParseError{Err: err, Str: record[3], Field: "Player.NumVillages"}
|
||||
}
|
||||
|
||||
player.Points, err = strconv.Atoi(record[4])
|
||||
if err != nil {
|
||||
return Player{}, ParseError{Err: err, Str: record[4], Field: "Player.Points"}
|
||||
}
|
||||
|
||||
player.Rank, err = strconv.Atoi(record[5])
|
||||
if err != nil {
|
||||
return Player{}, ParseError{Err: err, Str: record[5], Field: "Player.Rank"}
|
||||
}
|
||||
|
||||
player.OpponentsDefeated = p.od[player.ID]
|
||||
|
||||
player.ProfileURL = buildURLWithQuery(p.baseURL, endpointGame, fmt.Sprintf(queryPlayerProfile, player.ID)).String()
|
||||
|
||||
return player, nil
|
||||
}
|
||||
|
||||
type villageCSVParser struct {
|
||||
r io.Reader
|
||||
baseURL *url.URL
|
||||
}
|
||||
|
||||
const fieldsPerRecordVillage = 7
|
||||
|
||||
func (v villageCSVParser) parse() ([]Village, error) {
|
||||
csvR := newCSVReader(v.r, fieldsPerRecordVillage)
|
||||
|
||||
var villages []Village
|
||||
for {
|
||||
rec, err := csvR.Read()
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
village, err := v.parseRecord(rec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
villages = append(villages, village)
|
||||
}
|
||||
|
||||
slices.SortFunc(villages, func(a, b Village) int {
|
||||
return cmp.Compare(a.ID, b.ID)
|
||||
})
|
||||
|
||||
return villages, nil
|
||||
}
|
||||
|
||||
func (v villageCSVParser) parseRecord(record []string) (Village, error) {
|
||||
var err error
|
||||
var village Village
|
||||
|
||||
// $id, $name, $x, $y, $player, $points, $bonus_id
|
||||
|
||||
village.ID, err = strconv.Atoi(record[0])
|
||||
if err != nil {
|
||||
return Village{}, ParseError{Err: err, Str: record[0], Field: "Village.ID"}
|
||||
}
|
||||
|
||||
village.Name, err = url.QueryUnescape(record[1])
|
||||
if err != nil {
|
||||
return Village{}, ParseError{Err: err, Str: record[1], Field: "Village.Name"}
|
||||
}
|
||||
|
||||
village.X, err = strconv.Atoi(record[2])
|
||||
if err != nil {
|
||||
return Village{}, ParseError{Err: err, Str: record[2], Field: "Village.X"}
|
||||
}
|
||||
|
||||
village.Y, err = strconv.Atoi(record[3])
|
||||
if err != nil {
|
||||
return Village{}, ParseError{Err: err, Str: record[3], Field: "Village.Y"}
|
||||
}
|
||||
|
||||
village.PlayerID, err = strconv.Atoi(record[4])
|
||||
if err != nil {
|
||||
return Village{}, ParseError{Err: err, Str: record[4], Field: "Village.PlayerID"}
|
||||
}
|
||||
|
||||
village.Points, err = strconv.Atoi(record[5])
|
||||
if err != nil {
|
||||
return Village{}, ParseError{Err: err, Str: record[5], Field: "Village.Points"}
|
||||
}
|
||||
|
||||
village.Bonus, err = strconv.Atoi(record[6])
|
||||
if err != nil {
|
||||
return Village{}, ParseError{Err: err, Str: record[6], Field: "Village.Bonus"}
|
||||
}
|
||||
|
||||
village.Continent = v.buildContinent(record, village.X, village.Y)
|
||||
|
||||
village.ProfileURL = buildURLWithQuery(v.baseURL, endpointGame, fmt.Sprintf(queryVillageProfile, village.ID)).String()
|
||||
|
||||
return village, nil
|
||||
}
|
||||
|
||||
func (v villageCSVParser) buildContinent(record []string, x, y int) string {
|
||||
continent := "K"
|
||||
|
||||
switch {
|
||||
case x < 100 && y < 100:
|
||||
continent += "0"
|
||||
case x > 100 && y < 100:
|
||||
continent += string(record[2][0])
|
||||
case x < 100 && y > 100:
|
||||
continent += string(record[3][0]) + "0"
|
||||
default:
|
||||
continent += string(record[3][0]) + string(record[2][0])
|
||||
}
|
||||
|
||||
return continent
|
||||
}
|
||||
|
||||
type odCSVParser struct {
|
||||
r io.Reader
|
||||
}
|
||||
|
||||
type odRecord struct {
|
||||
ID int
|
||||
Rank int
|
||||
Score int
|
||||
}
|
||||
|
||||
const fieldsPerRecordOD = 3
|
||||
|
||||
func (o odCSVParser) parse() ([]odRecord, error) {
|
||||
csvR := newCSVReader(o.r, fieldsPerRecordOD)
|
||||
|
||||
var odRecords []odRecord
|
||||
for {
|
||||
rec, err := csvR.Read()
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
od, err := o.parseRecord(rec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
odRecords = append(odRecords, od)
|
||||
}
|
||||
|
||||
return odRecords, nil
|
||||
}
|
||||
|
||||
func (o odCSVParser) parseRecord(record []string) (odRecord, error) {
|
||||
var err error
|
||||
var rec odRecord
|
||||
|
||||
// $rank, $id, $score
|
||||
|
||||
rec.Rank, err = strconv.Atoi(record[0])
|
||||
if err != nil {
|
||||
return odRecord{}, ParseError{Err: err, Str: record[0], Field: "odRecord.Rank"}
|
||||
}
|
||||
|
||||
rec.ID, err = strconv.Atoi(record[1])
|
||||
if err != nil {
|
||||
return odRecord{}, ParseError{Err: err, Str: record[1], Field: "odRecord.ID"}
|
||||
}
|
||||
|
||||
rec.Score, err = strconv.Atoi(record[2])
|
||||
if err != nil {
|
||||
return odRecord{}, ParseError{Err: err, Str: record[2], Field: "odRecord.Score"}
|
||||
}
|
||||
|
||||
return rec, nil
|
||||
}
|
||||
|
||||
type ennoblementCSVParser struct {
|
||||
r io.Reader
|
||||
since time.Time
|
||||
}
|
||||
|
||||
const fieldsPerRecordEnnoblement = 7
|
||||
|
||||
func (e ennoblementCSVParser) parse() ([]Ennoblement, error) {
|
||||
csvR := newCSVReader(e.r, fieldsPerRecordEnnoblement)
|
||||
|
||||
var ennoblements []Ennoblement
|
||||
for {
|
||||
rec, err := csvR.Read()
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ennoblement, err := e.parseRecord(rec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ennoblement.CreatedAt.Before(e.since) {
|
||||
continue
|
||||
}
|
||||
|
||||
ennoblements = append(ennoblements, ennoblement)
|
||||
}
|
||||
|
||||
slices.SortFunc(ennoblements, func(a, b Ennoblement) int {
|
||||
return a.CreatedAt.Compare(b.CreatedAt)
|
||||
})
|
||||
|
||||
return ennoblements, nil
|
||||
}
|
||||
|
||||
func (e ennoblementCSVParser) parseRecord(record []string) (Ennoblement, error) {
|
||||
var err error
|
||||
var ennoblement Ennoblement
|
||||
|
||||
// $village_id, $unix_timestamp, $new_owner, $old_owner, $old_tribe_id, $new_tribe_id, $points
|
||||
|
||||
ennoblement.VillageID, err = strconv.Atoi(record[0])
|
||||
if err != nil {
|
||||
return Ennoblement{}, ParseError{Err: err, Str: record[0], Field: "Ennoblement.VillageID"}
|
||||
}
|
||||
|
||||
ennoblement.CreatedAt, err = e.parseTimestamp(record[1])
|
||||
if err != nil {
|
||||
return Ennoblement{}, ParseError{Err: err, Str: record[1], Field: "Ennoblement.CreatedAt"}
|
||||
}
|
||||
|
||||
ennoblement.NewOwnerID, err = strconv.Atoi(record[2])
|
||||
if err != nil {
|
||||
return Ennoblement{}, ParseError{Err: err, Str: record[2], Field: "Ennoblement.NewOwnerID"}
|
||||
}
|
||||
|
||||
ennoblement.OldOwnerID, err = strconv.Atoi(record[3])
|
||||
if err != nil {
|
||||
return Ennoblement{}, ParseError{Err: err, Str: record[3], Field: "Ennoblement.OldOwnerID"}
|
||||
}
|
||||
|
||||
ennoblement.OldTribeID, err = strconv.Atoi(record[4])
|
||||
if err != nil {
|
||||
return Ennoblement{}, ParseError{Err: err, Str: record[4], Field: "Ennoblement.OldTribeID"}
|
||||
}
|
||||
|
||||
ennoblement.NewTribeID, err = strconv.Atoi(record[5])
|
||||
if err != nil {
|
||||
return Ennoblement{}, ParseError{Err: err, Str: record[5], Field: "Ennoblement.NewTribeID"}
|
||||
}
|
||||
|
||||
ennoblement.Points, err = strconv.Atoi(record[6])
|
||||
if err != nil {
|
||||
return Ennoblement{}, ParseError{Err: err, Str: record[6], Field: "Ennoblement.Points"}
|
||||
}
|
||||
|
||||
return ennoblement, nil
|
||||
}
|
||||
|
||||
func (e ennoblementCSVParser) parseTimestamp(s string) (time.Time, error) {
|
||||
timestamp, err := strconv.ParseInt(s, 10, 64)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
return time.Unix(timestamp, 0), nil
|
||||
}
|
||||
|
||||
func newCSVReader(r io.Reader, fieldsPerRecord int) *csv.Reader {
|
||||
csvR := csv.NewReader(r)
|
||||
csvR.Comma = ','
|
||||
csvR.FieldsPerRecord = fieldsPerRecord
|
||||
csvR.ReuseRecord = true
|
||||
return csvR
|
||||
}
|
||||
|
||||
//nolint:revive
|
||||
func buildODURLs(base *url.URL, tribe bool) [4]*url.URL {
|
||||
// there is no endpoint to get opponents defeated as supporter for tribes :(
|
||||
if tribe {
|
||||
return [4]*url.URL{
|
||||
buildURL(base, endpointTribesOD),
|
||||
buildURL(base, endpointTribesODA),
|
||||
buildURL(base, endpointTribesODD),
|
||||
nil,
|
||||
}
|
||||
}
|
||||
return [4]*url.URL{
|
||||
buildURL(base, endpointPlayersOD),
|
||||
buildURL(base, endpointPlayersODA),
|
||||
buildURL(base, endpointPlayersODD),
|
||||
buildURL(base, endpointPlayersODS),
|
||||
}
|
||||
}
|
||||
|
||||
func buildURL(base *url.URL, path string) *url.URL {
|
||||
return buildURLWithQuery(base, path, "")
|
||||
}
|
||||
|
||||
func buildURLWithQuery(base *url.URL, path, query string) *url.URL {
|
||||
return &url.URL{
|
||||
Scheme: base.Scheme,
|
||||
Host: base.Host,
|
||||
Path: path,
|
||||
RawQuery: query,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package tw
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultUserAgent = "tribalwarshelp/development"
|
||||
defaultTimeout = 10 * time.Second
|
||||
)
|
||||
|
||||
type EnnoblementsUseInterfaceFunc func(since time.Time) bool
|
||||
|
||||
type clientConfig struct {
|
||||
userAgent string
|
||||
client *http.Client
|
||||
ennoblementsUseInterfaceFunc EnnoblementsUseInterfaceFunc
|
||||
}
|
||||
|
||||
type ClientOption func(c *clientConfig)
|
||||
|
||||
func newClientConfig(opts ...ClientOption) *clientConfig {
|
||||
cfg := &clientConfig{
|
||||
userAgent: defaultUserAgent,
|
||||
client: &http.Client{
|
||||
Timeout: defaultTimeout,
|
||||
},
|
||||
ennoblementsUseInterfaceFunc: func(since time.Time) bool {
|
||||
return since.After(time.Now().Add(-23 * time.Hour))
|
||||
},
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(cfg)
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
func WithUserAgent(ua string) ClientOption {
|
||||
return func(cfg *clientConfig) {
|
||||
cfg.userAgent = ua
|
||||
}
|
||||
}
|
||||
|
||||
func WithHTTPClient(hc *http.Client) ClientOption {
|
||||
return func(cfg *clientConfig) {
|
||||
cfg.client = hc
|
||||
}
|
||||
}
|
||||
|
||||
// WithEnnoblementsUseInterfaceFunc takes a function that will be called on every Client.GetEnnoblements call
|
||||
// to determine whether the client should use /interface.php?func=get_conquer_extended or /map/conquer_extended.txt
|
||||
// The default function checks whether since is after now() - 23h,
|
||||
// if so, it calls /map/conquer_extended.txt, otherwise /interface.php?func=get_conquer_extended.
|
||||
func WithEnnoblementsUseInterfaceFunc(f EnnoblementsUseInterfaceFunc) ClientOption {
|
||||
return func(cfg *clientConfig) {
|
||||
cfg.ennoblementsUseInterfaceFunc = f
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,15 @@
|
|||
package tw
|
||||
|
||||
type ParseError struct {
|
||||
Field string
|
||||
Str string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e ParseError) Error() string {
|
||||
return e.Field + ": parsing '" + e.Str + "': " + e.Err.Error()
|
||||
}
|
||||
|
||||
func (e ParseError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
|
@ -0,0 +1,186 @@
|
|||
package tw
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ServerConfigBuild struct {
|
||||
Destroy int `xml:"destroy"`
|
||||
}
|
||||
|
||||
type ServerConfigMisc struct {
|
||||
KillRanking int `xml:"kill_ranking"`
|
||||
Tutorial int `xml:"tutorial"`
|
||||
TradeCancelTime int `xml:"trade_cancel_time"`
|
||||
}
|
||||
|
||||
type ServerConfigCommands struct {
|
||||
MillisArrival int `xml:"millis_arrival"`
|
||||
CommandCancelTime int `xml:"command_cancel_time"`
|
||||
}
|
||||
|
||||
type ServerConfigNewbie struct {
|
||||
Days int `xml:"days"`
|
||||
RatioDays int `xml:"ratio_days"`
|
||||
Ratio int `xml:"ratio"`
|
||||
RemoveNewbieVillages int `xml:"removeNewbieVillages"`
|
||||
}
|
||||
|
||||
type KnightNewItems int
|
||||
|
||||
func (k KnightNewItems) Int() int {
|
||||
return int(k)
|
||||
}
|
||||
|
||||
func (k *KnightNewItems) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||
var s string
|
||||
|
||||
if err := d.DecodeElement(&s, &start); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch strings.ToUpper(s) {
|
||||
case "ON", "1":
|
||||
*k = 1
|
||||
case "OFF", "0", "":
|
||||
*k = 0
|
||||
default:
|
||||
return xml.UnmarshalError(fmt.Sprintf(`KnightNewItems: parsing "%s": invalid syntax`, s))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type ServerConfigGame struct {
|
||||
BuildtimeFormula int `xml:"buildtime_formula"`
|
||||
Knight int `xml:"knight"`
|
||||
KnightNewItems KnightNewItems `xml:"knight_new_items"`
|
||||
Archer int `xml:"archer"`
|
||||
Tech int `xml:"tech"`
|
||||
FarmLimit int `xml:"farm_limit"`
|
||||
Church int `xml:"church"`
|
||||
Watchtower int `xml:"watchtower"`
|
||||
Stronghold int `xml:"stronghold"`
|
||||
FakeLimit float64 `xml:"fake_limit"`
|
||||
BarbarianRise float64 `xml:"barbarian_rise"`
|
||||
BarbarianShrink int `xml:"barbarian_shrink"`
|
||||
BarbarianMaxPoints int `xml:"barbarian_max_points"`
|
||||
Scavenging int `xml:"scavenging"`
|
||||
Hauls int `xml:"hauls"`
|
||||
HaulsBase int `xml:"hauls_base"`
|
||||
HaulsMax int `xml:"hauls_max"`
|
||||
BaseProduction int `xml:"base_production"`
|
||||
Event int `xml:"event"`
|
||||
SuppressEvents int `xml:"suppress_events"`
|
||||
}
|
||||
|
||||
type ServerConfigBuildings struct {
|
||||
CustomMain int `xml:"custom_main"`
|
||||
CustomFarm int `xml:"custom_farm"`
|
||||
CustomStorage int `xml:"custom_storage"`
|
||||
CustomPlace int `xml:"custom_place"`
|
||||
CustomBarracks int `xml:"custom_barracks"`
|
||||
CustomChurch int `xml:"custom_church"`
|
||||
CustomSmith int `xml:"custom_smith"`
|
||||
CustomWood int `xml:"custom_wood"`
|
||||
CustomStone int `xml:"custom_stone"`
|
||||
CustomIron int `xml:"custom_iron"`
|
||||
CustomMarket int `xml:"custom_market"`
|
||||
CustomStable int `xml:"custom_stable"`
|
||||
CustomWall int `xml:"custom_wall"`
|
||||
CustomGarage int `xml:"custom_garage"`
|
||||
CustomHide int `xml:"custom_hide"`
|
||||
CustomSnob int `xml:"custom_snob"`
|
||||
CustomStatue int `xml:"custom_statue"`
|
||||
CustomWatchtower int `xml:"custom_watchtower"`
|
||||
}
|
||||
|
||||
type ServerConfigSnob struct {
|
||||
Gold int `xml:"gold"`
|
||||
CheapRebuild int `xml:"cheap_rebuild"`
|
||||
Rise int `xml:"rise"`
|
||||
MaxDist int `xml:"max_dist"`
|
||||
Factor float64 `xml:"factor"`
|
||||
CoinWood int `xml:"coin_wood"`
|
||||
CoinStone int `xml:"coin_stone"`
|
||||
CoinIron int `xml:"coin_iron"`
|
||||
NoBarbConquer int `xml:"no_barb_conquer"`
|
||||
}
|
||||
|
||||
type ServerConfigAlly struct {
|
||||
NoHarm int `xml:"no_harm"`
|
||||
NoOtherSupport int `xml:"no_other_support"`
|
||||
NoOtherSupportType int `xml:"no_other_support_type"`
|
||||
AllytimeSupport int `xml:"allytime_support"`
|
||||
NoLeave int `xml:"no_leave"`
|
||||
NoJoin int `xml:"no_join"`
|
||||
Limit int `xml:"limit"`
|
||||
FixedAllies int `xml:"fixed_allies"`
|
||||
PointsMemberCount int `xml:"points_member_count"`
|
||||
WarsMemberRequirement int `xml:"wars_member_requirement"`
|
||||
WarsPointsRequirement int `xml:"wars_points_requirement"`
|
||||
WarsAutoacceptDays int `xml:"wars_autoaccept_days"`
|
||||
Levels int `xml:"levels"`
|
||||
XpRequirements string `xml:"xp_requirements"`
|
||||
}
|
||||
|
||||
type ServerConfigCoord struct {
|
||||
MapSize int `xml:"map_size"`
|
||||
Func int `xml:"func"`
|
||||
EmptyVillages int `xml:"empty_villages"`
|
||||
BonusVillages int `xml:"bonus_villages"`
|
||||
BonusNew int `xml:"bonus_new"`
|
||||
Inner int `xml:"inner"`
|
||||
SelectStart int `xml:"select_start"`
|
||||
VillageMoveWait int `xml:"village_move_wait"`
|
||||
NobleRestart int `xml:"noble_restart"`
|
||||
StartVillages int `xml:"start_villages"`
|
||||
}
|
||||
|
||||
type ServerConfigSitter struct {
|
||||
Allow int `xml:"allow"`
|
||||
}
|
||||
|
||||
type ServerConfigSleep struct {
|
||||
Active int `xml:"active"`
|
||||
Delay int `xml:"delay"`
|
||||
Min int `xml:"min"`
|
||||
Max int `xml:"max"`
|
||||
MinAwake int `xml:"min_awake"`
|
||||
MaxAwake int `xml:"max_awake"`
|
||||
WarnTime int `xml:"warn_time"`
|
||||
}
|
||||
|
||||
type ServerConfigNight struct {
|
||||
Active int `xml:"active"`
|
||||
StartHour int `xml:"start_hour"`
|
||||
EndHour int `xml:"end_hour"`
|
||||
DefFactor float64 `xml:"def_factor"`
|
||||
Duration int `xml:"duration"`
|
||||
}
|
||||
|
||||
type ServerConfigWin struct {
|
||||
Check int `xml:"check"`
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
XMLName xml.Name `xml:"config"`
|
||||
Speed float64 `xml:"speed"`
|
||||
UnitSpeed float64 `xml:"unit_speed"`
|
||||
Moral int `xml:"moral"`
|
||||
Build ServerConfigBuild `xml:"build"`
|
||||
Misc ServerConfigMisc `xml:"misc"`
|
||||
Commands ServerConfigCommands `xml:"commands"`
|
||||
Newbie ServerConfigNewbie `xml:"newbie"`
|
||||
Game ServerConfigGame `xml:"game"`
|
||||
Buildings ServerConfigBuildings `xml:"buildings"`
|
||||
Snob ServerConfigSnob `xml:"snob"`
|
||||
Ally ServerConfigAlly `xml:"ally"`
|
||||
Coord ServerConfigCoord `xml:"coord"`
|
||||
Sitter ServerConfigSitter `xml:"sitter"`
|
||||
Sleep ServerConfigSleep `xml:"sleep"`
|
||||
Night ServerConfigNight `xml:"night"`
|
||||
Win ServerConfigWin `xml:"win"`
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package tw_test
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"testing"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/tw"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestKnightNewItems_UnmarshalXML(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
data string
|
||||
expected tw.KnightNewItems
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
data: "<knight_new_items/>",
|
||||
expected: 0,
|
||||
},
|
||||
{
|
||||
data: "<knight_new_items>Off</knight_new_items>",
|
||||
expected: 0,
|
||||
},
|
||||
{
|
||||
data: "<knight_new_items>0</knight_new_items>",
|
||||
expected: 0,
|
||||
},
|
||||
{
|
||||
data: "<knight_new_items>On</knight_new_items>",
|
||||
expected: 1,
|
||||
},
|
||||
{
|
||||
data: "<knight_new_items>ON</knight_new_items>",
|
||||
expected: 1,
|
||||
},
|
||||
{
|
||||
data: "<knight_new_items>on</knight_new_items>",
|
||||
expected: 1,
|
||||
},
|
||||
{
|
||||
data: "<knight_new_items>1</knight_new_items>",
|
||||
expected: 1,
|
||||
},
|
||||
{
|
||||
data: "<knight_new_items>invalid value</knight_new_items>",
|
||||
expectedErr: xml.UnmarshalError(`KnightNewItems: parsing "invalid value": invalid syntax`),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
|
||||
t.Run(tt.data, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var res tw.KnightNewItems
|
||||
require.ErrorIs(t, tt.expectedErr, xml.Unmarshal([]byte(tt.data), &res))
|
||||
assert.Equal(t, tt.expected, res)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,241 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
|
||||
<config>
|
||||
<main>
|
||||
<max_level>30</max_level>
|
||||
<min_level>1</min_level>
|
||||
<wood>90</wood>
|
||||
<stone>80</stone>
|
||||
<iron>70</iron>
|
||||
<pop>5</pop>
|
||||
<wood_factor>1.26</wood_factor>
|
||||
<stone_factor>1.275</stone_factor>
|
||||
<iron_factor>1.26</iron_factor>
|
||||
<pop_factor>1.17</pop_factor>
|
||||
<build_time>900</build_time>
|
||||
<build_time_factor>1.2</build_time_factor>
|
||||
</main>
|
||||
<barracks>
|
||||
<max_level>25</max_level>
|
||||
<min_level>0</min_level>
|
||||
<wood>200</wood>
|
||||
<stone>170</stone>
|
||||
<iron>90</iron>
|
||||
<pop>7</pop>
|
||||
<wood_factor>1.26</wood_factor>
|
||||
<stone_factor>1.28</stone_factor>
|
||||
<iron_factor>1.26</iron_factor>
|
||||
<pop_factor>1.17</pop_factor>
|
||||
<build_time>1800</build_time>
|
||||
<build_time_factor>1.2</build_time_factor>
|
||||
</barracks>
|
||||
<stable>
|
||||
<max_level>20</max_level>
|
||||
<min_level>0</min_level>
|
||||
<wood>270</wood>
|
||||
<stone>240</stone>
|
||||
<iron>260</iron>
|
||||
<pop>8</pop>
|
||||
<wood_factor>1.26</wood_factor>
|
||||
<stone_factor>1.28</stone_factor>
|
||||
<iron_factor>1.26</iron_factor>
|
||||
<pop_factor>1.17</pop_factor>
|
||||
<build_time>6000</build_time>
|
||||
<build_time_factor>1.2</build_time_factor>
|
||||
</stable>
|
||||
<garage>
|
||||
<max_level>15</max_level>
|
||||
<min_level>0</min_level>
|
||||
<wood>300</wood>
|
||||
<stone>240</stone>
|
||||
<iron>260</iron>
|
||||
<pop>8</pop>
|
||||
<wood_factor>1.26</wood_factor>
|
||||
<stone_factor>1.28</stone_factor>
|
||||
<iron_factor>1.26</iron_factor>
|
||||
<pop_factor>1.17</pop_factor>
|
||||
<build_time>6000</build_time>
|
||||
<build_time_factor>1.2</build_time_factor>
|
||||
</garage>
|
||||
<watchtower>
|
||||
<max_level>20</max_level>
|
||||
<min_level>0</min_level>
|
||||
<wood>12000</wood>
|
||||
<stone>14000</stone>
|
||||
<iron>10000</iron>
|
||||
<pop>500</pop>
|
||||
<wood_factor>1.17</wood_factor>
|
||||
<stone_factor>1.17</stone_factor>
|
||||
<iron_factor>1.18</iron_factor>
|
||||
<pop_factor>1.18</pop_factor>
|
||||
<build_time>13200</build_time>
|
||||
<build_time_factor>1.2</build_time_factor>
|
||||
</watchtower>
|
||||
<snob>
|
||||
<max_level>1</max_level>
|
||||
<min_level>0</min_level>
|
||||
<wood>15000</wood>
|
||||
<stone>25000</stone>
|
||||
<iron>10000</iron>
|
||||
<pop>80</pop>
|
||||
<wood_factor>2</wood_factor>
|
||||
<stone_factor>2</stone_factor>
|
||||
<iron_factor>2</iron_factor>
|
||||
<pop_factor>1.17</pop_factor>
|
||||
<build_time>586800</build_time>
|
||||
<build_time_factor>1.2</build_time_factor>
|
||||
</snob>
|
||||
<smith>
|
||||
<max_level>20</max_level>
|
||||
<min_level>0</min_level>
|
||||
<wood>220</wood>
|
||||
<stone>180</stone>
|
||||
<iron>240</iron>
|
||||
<pop>20</pop>
|
||||
<wood_factor>1.26</wood_factor>
|
||||
<stone_factor>1.275</stone_factor>
|
||||
<iron_factor>1.26</iron_factor>
|
||||
<pop_factor>1.17</pop_factor>
|
||||
<build_time>6000</build_time>
|
||||
<build_time_factor>1.2</build_time_factor>
|
||||
</smith>
|
||||
<place>
|
||||
<max_level>1</max_level>
|
||||
<min_level>0</min_level>
|
||||
<wood>10</wood>
|
||||
<stone>40</stone>
|
||||
<iron>30</iron>
|
||||
<pop>0</pop>
|
||||
<wood_factor>1.26</wood_factor>
|
||||
<stone_factor>1.275</stone_factor>
|
||||
<iron_factor>1.26</iron_factor>
|
||||
<pop_factor>1.17</pop_factor>
|
||||
<build_time>10860</build_time>
|
||||
<build_time_factor>1.2</build_time_factor>
|
||||
</place>
|
||||
<statue>
|
||||
<max_level>1</max_level>
|
||||
<min_level>0</min_level>
|
||||
<wood>220</wood>
|
||||
<stone>220</stone>
|
||||
<iron>220</iron>
|
||||
<pop>10</pop>
|
||||
<wood_factor>1.26</wood_factor>
|
||||
<stone_factor>1.275</stone_factor>
|
||||
<iron_factor>1.26</iron_factor>
|
||||
<pop_factor>1.17</pop_factor>
|
||||
<build_time>1500</build_time>
|
||||
<build_time_factor>1.2</build_time_factor>
|
||||
</statue>
|
||||
<market>
|
||||
<max_level>25</max_level>
|
||||
<min_level>0</min_level>
|
||||
<wood>100</wood>
|
||||
<stone>100</stone>
|
||||
<iron>100</iron>
|
||||
<pop>20</pop>
|
||||
<wood_factor>1.26</wood_factor>
|
||||
<stone_factor>1.275</stone_factor>
|
||||
<iron_factor>1.26</iron_factor>
|
||||
<pop_factor>1.17</pop_factor>
|
||||
<build_time>2700</build_time>
|
||||
<build_time_factor>1.2</build_time_factor>
|
||||
</market>
|
||||
<wood>
|
||||
<max_level>30</max_level>
|
||||
<min_level>0</min_level>
|
||||
<wood>50</wood>
|
||||
<stone>60</stone>
|
||||
<iron>40</iron>
|
||||
<pop>5</pop>
|
||||
<wood_factor>1.25</wood_factor>
|
||||
<stone_factor>1.275</stone_factor>
|
||||
<iron_factor>1.245</iron_factor>
|
||||
<pop_factor>1.155</pop_factor>
|
||||
<build_time>900</build_time>
|
||||
<build_time_factor>1.2</build_time_factor>
|
||||
</wood>
|
||||
<stone>
|
||||
<max_level>30</max_level>
|
||||
<min_level>0</min_level>
|
||||
<wood>65</wood>
|
||||
<stone>50</stone>
|
||||
<iron>40</iron>
|
||||
<pop>10</pop>
|
||||
<wood_factor>1.27</wood_factor>
|
||||
<stone_factor>1.265</stone_factor>
|
||||
<iron_factor>1.24</iron_factor>
|
||||
<pop_factor>1.14</pop_factor>
|
||||
<build_time>900</build_time>
|
||||
<build_time_factor>1.2</build_time_factor>
|
||||
</stone>
|
||||
<iron>
|
||||
<max_level>30</max_level>
|
||||
<min_level>0</min_level>
|
||||
<wood>75</wood>
|
||||
<stone>65</stone>
|
||||
<iron>70</iron>
|
||||
<pop>10</pop>
|
||||
<wood_factor>1.252</wood_factor>
|
||||
<stone_factor>1.275</stone_factor>
|
||||
<iron_factor>1.24</iron_factor>
|
||||
<pop_factor>1.17</pop_factor>
|
||||
<build_time>1080</build_time>
|
||||
<build_time_factor>1.2</build_time_factor>
|
||||
</iron>
|
||||
<farm>
|
||||
<max_level>30</max_level>
|
||||
<min_level>1</min_level>
|
||||
<wood>45</wood>
|
||||
<stone>40</stone>
|
||||
<iron>30</iron>
|
||||
<pop>0</pop>
|
||||
<wood_factor>1.3</wood_factor>
|
||||
<stone_factor>1.32</stone_factor>
|
||||
<iron_factor>1.29</iron_factor>
|
||||
<pop_factor>1</pop_factor>
|
||||
<build_time>1200</build_time>
|
||||
<build_time_factor>1.2</build_time_factor>
|
||||
</farm>
|
||||
<storage>
|
||||
<max_level>30</max_level>
|
||||
<min_level>1</min_level>
|
||||
<wood>60</wood>
|
||||
<stone>50</stone>
|
||||
<iron>40</iron>
|
||||
<pop>0</pop>
|
||||
<wood_factor>1.265</wood_factor>
|
||||
<stone_factor>1.27</stone_factor>
|
||||
<iron_factor>1.245</iron_factor>
|
||||
<pop_factor>1.15</pop_factor>
|
||||
<build_time>1020</build_time>
|
||||
<build_time_factor>1.2</build_time_factor>
|
||||
</storage>
|
||||
<hide>
|
||||
<max_level>10</max_level>
|
||||
<min_level>0</min_level>
|
||||
<wood>50</wood>
|
||||
<stone>60</stone>
|
||||
<iron>50</iron>
|
||||
<pop>2</pop>
|
||||
<wood_factor>1.25</wood_factor>
|
||||
<stone_factor>1.25</stone_factor>
|
||||
<iron_factor>1.25</iron_factor>
|
||||
<pop_factor>1.17</pop_factor>
|
||||
<build_time>1800</build_time>
|
||||
<build_time_factor>1.2</build_time_factor>
|
||||
</hide>
|
||||
<wall>
|
||||
<max_level>20</max_level>
|
||||
<min_level>0</min_level>
|
||||
<wood>50</wood>
|
||||
<stone>100</stone>
|
||||
<iron>20</iron>
|
||||
<pop>5</pop>
|
||||
<wood_factor>1.26</wood_factor>
|
||||
<stone_factor>1.275</stone_factor>
|
||||
<iron_factor>1.26</iron_factor>
|
||||
<pop_factor>1.17</pop_factor>
|
||||
<build_time>3600</build_time>
|
||||
<build_time_factor>1.2</build_time_factor>
|
||||
</wall>
|
||||
</config>
|
|
@ -0,0 +1 @@
|
|||
a:16:{s:5:"pl159";s:25:"https://pl159.plemiona.pl";s:5:"pl161";s:25:"https://pl161.plemiona.pl";s:5:"pl164";s:25:"https://pl164.plemiona.pl";s:4:"plc1";s:24:"https://plc1.plemiona.pl";s:4:"pls1";s:24:"https://pls1.plemiona.pl";s:4:"plp7";s:24:"https://plp7.plemiona.pl";s:5:"pl165";s:25:"https://pl165.plemiona.pl";s:5:"pl167";s:25:"https://pl167.plemiona.pl";s:5:"pl168";s:25:"https://pl168.plemiona.pl";s:4:"plp8";s:24:"https://plp8.plemiona.pl";s:5:"pl169";s:25:"https://pl169.plemiona.pl";s:5:"pl170";s:25:"https://pl170.plemiona.pl";s:5:"pl171";s:25:"https://pl171.plemiona.pl";s:5:"pl172";s:25:"https://pl172.plemiona.pl";s:5:"pl173";s:25:"https://pl173.plemiona.pl";s:5:"pl174";s:25:"https://pl174.plemiona.pl";}
|
|
@ -0,0 +1,125 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
|
||||
<config>
|
||||
<speed>4.5</speed>
|
||||
<unit_speed>0.5</unit_speed>
|
||||
<moral>1</moral>
|
||||
<build>
|
||||
<destroy>1</destroy>
|
||||
</build>
|
||||
<misc>
|
||||
<kill_ranking>2</kill_ranking>
|
||||
<tutorial>0</tutorial>
|
||||
<trade_cancel_time>300</trade_cancel_time>
|
||||
</misc>
|
||||
<commands>
|
||||
<millis_arrival>0</millis_arrival>
|
||||
<command_cancel_time>600</command_cancel_time>
|
||||
</commands>
|
||||
<newbie>
|
||||
<days>3</days>
|
||||
<ratio_days>15</ratio_days>
|
||||
<ratio>10</ratio>
|
||||
<removeNewbieVillages>1</removeNewbieVillages>
|
||||
</newbie>
|
||||
<game>
|
||||
<buildtime_formula>2</buildtime_formula>
|
||||
<knight>0</knight>
|
||||
<knight_new_items>On</knight_new_items>
|
||||
<archer>1</archer>
|
||||
<tech>2</tech>
|
||||
<farm_limit>0</farm_limit>
|
||||
<church>0</church>
|
||||
<watchtower>0</watchtower>
|
||||
<stronghold>0</stronghold>
|
||||
<fake_limit>0.5</fake_limit>
|
||||
<barbarian_rise>0.001</barbarian_rise>
|
||||
<barbarian_shrink>1</barbarian_shrink>
|
||||
<barbarian_max_points>800</barbarian_max_points>
|
||||
<scavenging>1</scavenging>
|
||||
<hauls>2</hauls>
|
||||
<hauls_base>5000</hauls_base>
|
||||
<hauls_max>1000000</hauls_max>
|
||||
<base_production>75</base_production>
|
||||
<event>0</event>
|
||||
<suppress_events>1</suppress_events>
|
||||
</game>
|
||||
<buildings>
|
||||
<custom_main>-1</custom_main>
|
||||
<custom_farm>-1</custom_farm>
|
||||
<custom_storage>-1</custom_storage>
|
||||
<custom_place>-1</custom_place>
|
||||
<custom_barracks>-1</custom_barracks>
|
||||
<custom_church>-1</custom_church>
|
||||
<custom_smith>-1</custom_smith>
|
||||
<custom_wood>-1</custom_wood>
|
||||
<custom_stone>-1</custom_stone>
|
||||
<custom_iron>-1</custom_iron>
|
||||
<custom_market>-1</custom_market>
|
||||
<custom_stable>-1</custom_stable>
|
||||
<custom_wall>-1</custom_wall>
|
||||
<custom_garage>-1</custom_garage>
|
||||
<custom_hide>-1</custom_hide>
|
||||
<custom_snob>-1</custom_snob>
|
||||
<custom_statue>-1</custom_statue>
|
||||
<custom_watchtower>-1</custom_watchtower>
|
||||
</buildings>
|
||||
<snob>
|
||||
<gold>1</gold>
|
||||
<cheap_rebuild>0</cheap_rebuild>
|
||||
<rise>2</rise>
|
||||
<max_dist>50</max_dist>
|
||||
<factor>1</factor>
|
||||
<coin_wood>28000</coin_wood>
|
||||
<coin_stone>30000</coin_stone>
|
||||
<coin_iron>25000</coin_iron>
|
||||
<no_barb_conquer>0</no_barb_conquer>
|
||||
</snob>
|
||||
<ally>
|
||||
<no_harm>0</no_harm>
|
||||
<no_other_support>0</no_other_support>
|
||||
<no_other_support_type>0</no_other_support_type>
|
||||
<allytime_support>0</allytime_support>
|
||||
<no_leave></no_leave>
|
||||
<no_join></no_join>
|
||||
<limit>15</limit>
|
||||
<fixed_allies>0</fixed_allies>
|
||||
<wars_member_requirement>5</wars_member_requirement>
|
||||
<wars_points_requirement>15000</wars_points_requirement>
|
||||
<wars_autoaccept_days>7</wars_autoaccept_days>
|
||||
<levels>1</levels>
|
||||
<xp_requirements>v1</xp_requirements>
|
||||
</ally>
|
||||
<coord>
|
||||
<map_size>1000</map_size>
|
||||
<func>4</func>
|
||||
<empty_villages>11</empty_villages>
|
||||
<bonus_villages>1</bonus_villages>
|
||||
<inner>550</inner>
|
||||
<select_start>0</select_start>
|
||||
<village_move_wait>336</village_move_wait>
|
||||
<noble_restart>1</noble_restart>
|
||||
<start_villages>1</start_villages>
|
||||
</coord>
|
||||
<sitter>
|
||||
<allow>1</allow>
|
||||
</sitter>
|
||||
<sleep>
|
||||
<active>0</active>
|
||||
<delay>60</delay>
|
||||
<min>6</min>
|
||||
<max>10</max>
|
||||
<min_awake>12</min_awake>
|
||||
<max_awake>36</max_awake>
|
||||
<warn_time>10</warn_time>
|
||||
</sleep>
|
||||
<night>
|
||||
<active>2</active>
|
||||
<start_hour>23</start_hour>
|
||||
<end_hour>7</end_hour>
|
||||
<def_factor>3.5</def_factor>
|
||||
<duration>14</duration>
|
||||
</night>
|
||||
<win>
|
||||
<check>3</check>
|
||||
</win>
|
||||
</config>
|
|
@ -0,0 +1,123 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
|
||||
<config>
|
||||
<spear>
|
||||
<build_time>226.66666666667</build_time>
|
||||
<pop>1</pop>
|
||||
<speed>8</speed>
|
||||
<attack>10</attack>
|
||||
<defense>15</defense>
|
||||
<defense_cavalry>45</defense_cavalry>
|
||||
<defense_archer>20</defense_archer>
|
||||
<carry>25</carry>
|
||||
</spear>
|
||||
<sword>
|
||||
<build_time>333.33333333333</build_time>
|
||||
<pop>1</pop>
|
||||
<speed>9.7777777777778</speed>
|
||||
<attack>25</attack>
|
||||
<defense>50</defense>
|
||||
<defense_cavalry>15</defense_cavalry>
|
||||
<defense_archer>40</defense_archer>
|
||||
<carry>15</carry>
|
||||
</sword>
|
||||
<axe>
|
||||
<build_time>293.33333333333</build_time>
|
||||
<pop>1</pop>
|
||||
<speed>8</speed>
|
||||
<attack>40</attack>
|
||||
<defense>10</defense>
|
||||
<defense_cavalry>5</defense_cavalry>
|
||||
<defense_archer>10</defense_archer>
|
||||
<carry>10</carry>
|
||||
</axe>
|
||||
<archer>
|
||||
<build_time>400</build_time>
|
||||
<pop>1</pop>
|
||||
<speed>8</speed>
|
||||
<attack>15</attack>
|
||||
<defense>50</defense>
|
||||
<defense_cavalry>40</defense_cavalry>
|
||||
<defense_archer>5</defense_archer>
|
||||
<carry>10</carry>
|
||||
</archer>
|
||||
<spy>
|
||||
<build_time>200</build_time>
|
||||
<pop>2</pop>
|
||||
<speed>4</speed>
|
||||
<attack>0</attack>
|
||||
<defense>2</defense>
|
||||
<defense_cavalry>1</defense_cavalry>
|
||||
<defense_archer>2</defense_archer>
|
||||
<carry>0</carry>
|
||||
</spy>
|
||||
<light>
|
||||
<build_time>400</build_time>
|
||||
<pop>4</pop>
|
||||
<speed>4.4444444444444</speed>
|
||||
<attack>130</attack>
|
||||
<defense>30</defense>
|
||||
<defense_cavalry>40</defense_cavalry>
|
||||
<defense_archer>30</defense_archer>
|
||||
<carry>80</carry>
|
||||
</light>
|
||||
<marcher>
|
||||
<build_time>600</build_time>
|
||||
<pop>5</pop>
|
||||
<speed>4.4444444444444</speed>
|
||||
<attack>120</attack>
|
||||
<defense>40</defense>
|
||||
<defense_cavalry>30</defense_cavalry>
|
||||
<defense_archer>50</defense_archer>
|
||||
<carry>50</carry>
|
||||
</marcher>
|
||||
<heavy>
|
||||
<build_time>800</build_time>
|
||||
<pop>6</pop>
|
||||
<speed>4.8888888888889</speed>
|
||||
<attack>150</attack>
|
||||
<defense>200</defense>
|
||||
<defense_cavalry>80</defense_cavalry>
|
||||
<defense_archer>180</defense_archer>
|
||||
<carry>50</carry>
|
||||
</heavy>
|
||||
<ram>
|
||||
<build_time>1066.6666666667</build_time>
|
||||
<pop>5</pop>
|
||||
<speed>13.333333333333</speed>
|
||||
<attack>2</attack>
|
||||
<defense>20</defense>
|
||||
<defense_cavalry>50</defense_cavalry>
|
||||
<defense_archer>20</defense_archer>
|
||||
<carry>0</carry>
|
||||
</ram>
|
||||
<catapult>
|
||||
<build_time>1600</build_time>
|
||||
<pop>8</pop>
|
||||
<speed>13.333333333333</speed>
|
||||
<attack>100</attack>
|
||||
<defense>100</defense>
|
||||
<defense_cavalry>50</defense_cavalry>
|
||||
<defense_archer>100</defense_archer>
|
||||
<carry>0</carry>
|
||||
</catapult>
|
||||
<snob>
|
||||
<build_time>4000</build_time>
|
||||
<pop>100</pop>
|
||||
<speed>15.555555555556</speed>
|
||||
<attack>30</attack>
|
||||
<defense>100</defense>
|
||||
<defense_cavalry>50</defense_cavalry>
|
||||
<defense_archer>100</defense_archer>
|
||||
<carry>0</carry>
|
||||
</snob>
|
||||
<militia>
|
||||
<build_time>1</build_time>
|
||||
<pop>0</pop>
|
||||
<speed>0.016666666666667</speed>
|
||||
<attack>0</attack>
|
||||
<defense>15</defense>
|
||||
<defense_cavalry>45</defense_cavalry>
|
||||
<defense_archer>25</defense_archer>
|
||||
<carry>0</carry>
|
||||
</militia>
|
||||
</config>
|
|
@ -0,0 +1,69 @@
|
|||
package tw
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
Key string
|
||||
URL string
|
||||
}
|
||||
|
||||
type OpponentsDefeated struct {
|
||||
RankAtt int
|
||||
ScoreAtt int
|
||||
RankDef int
|
||||
ScoreDef int
|
||||
RankSup int
|
||||
ScoreSup int
|
||||
RankTotal int
|
||||
ScoreTotal int
|
||||
}
|
||||
|
||||
type Tribe struct {
|
||||
OpponentsDefeated
|
||||
|
||||
ID int
|
||||
Name string
|
||||
Tag string
|
||||
NumMembers int
|
||||
NumVillages int
|
||||
Points int
|
||||
AllPoints int
|
||||
Rank int
|
||||
ProfileURL string
|
||||
}
|
||||
|
||||
type Player struct {
|
||||
OpponentsDefeated
|
||||
|
||||
ID int
|
||||
Name string
|
||||
NumVillages int
|
||||
Points int
|
||||
Rank int
|
||||
TribeID int
|
||||
ProfileURL string
|
||||
}
|
||||
|
||||
type Village struct {
|
||||
ID int
|
||||
Name string
|
||||
Points int
|
||||
X int
|
||||
Y int
|
||||
Continent string
|
||||
Bonus int
|
||||
PlayerID int
|
||||
ProfileURL string
|
||||
}
|
||||
|
||||
type Ennoblement struct {
|
||||
VillageID int
|
||||
NewOwnerID int
|
||||
NewTribeID int
|
||||
OldOwnerID int
|
||||
OldTribeID int
|
||||
Points int
|
||||
CreatedAt time.Time
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package tw
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
)
|
||||
|
||||
type Unit struct {
|
||||
BuildTime float64 `xml:"build_time"`
|
||||
Pop int `xml:"pop"`
|
||||
Speed float64 `xml:"speed"`
|
||||
Attack int `xml:"attack"`
|
||||
Defense int `xml:"defense"`
|
||||
DefenseCavalry int `xml:"defense_cavalry"`
|
||||
DefenseArcher int `xml:"defense_archer"`
|
||||
Carry int `xml:"carry"`
|
||||
}
|
||||
|
||||
type UnitInfo struct {
|
||||
XMLName xml.Name `xml:"config"`
|
||||
Text string `xml:",chardata"`
|
||||
Spear Unit `xml:"spear"`
|
||||
Sword Unit `xml:"sword"`
|
||||
Axe Unit `xml:"axe"`
|
||||
Archer Unit `xml:"archer"`
|
||||
Spy Unit `xml:"spy"`
|
||||
Light Unit `xml:"light"`
|
||||
Marcher Unit `xml:"marcher"`
|
||||
Heavy Unit `xml:"heavy"`
|
||||
Ram Unit `xml:"ram"` //nolint:revive
|
||||
Catapult Unit `xml:"catapult"`
|
||||
Knight Unit `xml:"knight"`
|
||||
Snob Unit `xml:"snob"`
|
||||
Militia Unit `xml:"militia"`
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"labels": [
|
||||
"dependencies"
|
||||
],
|
||||
"extends": [
|
||||
"config:base",
|
||||
":semanticCommits",
|
||||
":semanticCommitTypeAll(chore)"
|
||||
],
|
||||
"postUpdateOptions": [
|
||||
"gomodTidy"
|
||||
],
|
||||
"ignorePaths": [
|
||||
".woodpecker/*"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
apiVersion: skaffold/v3
|
||||
kind: Config
|
||||
profiles:
|
||||
- name: dev
|
||||
build:
|
||||
tagPolicy:
|
||||
customTemplate:
|
||||
template: latest
|
||||
artifacts:
|
||||
- image: twhelp
|
||||
hooks:
|
||||
before:
|
||||
- command: [ "sh", "-c", "make generate" ]
|
||||
os: [ darwin, linux ]
|
||||
context: .
|
||||
docker:
|
||||
dockerfile: ./build/docker/twhelp/dev/Dockerfile
|
||||
manifests:
|
||||
kustomize:
|
||||
paths:
|
||||
- k8s/overlays/dev
|
||||
deploy:
|
||||
kubectl: {}
|
||||
- name: resources
|
||||
deploy:
|
||||
helm:
|
||||
releases:
|
||||
- name: twhelpdb
|
||||
repo: https://charts.bitnami.com/bitnami
|
||||
remoteChart: postgresql
|
||||
version: 13.2.24
|
||||
wait: true
|
||||
setValues:
|
||||
image.tag: 14.8.0-debian-11-r4
|
||||
auth.username: twhelp
|
||||
auth.password: twhelp
|
||||
auth.database: twhelp
|
||||
- name: twhelprmq
|
||||
repo: https://charts.bitnami.com/bitnami
|
||||
remoteChart: rabbitmq
|
||||
version: 12.5.6
|
||||
wait: true
|
||||
setValues:
|
||||
auth.username: twhelp
|
||||
auth.password: twhelp
|
Loading…
Reference in New Issue