Fix config map time limit and commands, add client password

This commit is contained in:
chris
2020-08-20 08:38:32 -04:00
committed by Chris Marshall
parent f92180af5e
commit f29bb81865
9 changed files with 123 additions and 37 deletions

View File

@ -9,7 +9,7 @@ q3: gen
gen: ## Generate and embed templates
@go run tools/genstatic.go public public
VERSION ?= v1.0.3
VERSION ?= v1.0.4
IMAGE ?= docker.io/criticalstack/quake:$(VERSION)
.PHONY: build

View File

@ -107,9 +107,49 @@ Any commands not captured by the config yaml can be specified in the `commands`
commands:
- seta g_inactivity 600
- seta sv_timeout 120
```
### Add bots
Bots can be added individually to map rotations using the `commands` section of the config:
```yaml
commands:
- addbot crash 1
- addbot sarge 2
```
The `addbot` server command requires the name of the bot and skill level (crash and sarge are a couple of the built-in bots).
Another way to add bots is by setting a minimum number of players to allow the server to add bots up to a certain value (removed when human players join):
```yaml
bot:
minPlayers: 8
game:
singlePlayerSkill: 2
```
`singlePlayerSkill` can be used to set the skill level of the automatically added bots (2 is the default skill level).
### Setting a password
A password should be set for the server to allow remote administration and is found in the server configuration settings:
```yaml
server:
password: "changeme"
```
This will allow clients to use `\rcon changeme <cmd>` to remotely administrate the server. To create a password that must be provided by clients to connect:
```yaml
game:
password: "letmein"
```
This will add an additional dialog to the in-browser client to accept the password. It will only appear if the server indicates it needs a password.
### Add custom maps
The content server hosts a small upload app to allow uploading `pk3` or `zip` files containing maps. The content server in the [example.yaml](example.yaml) shares a volume with the game server, effectively "side-loading" the map content, however, in the future the game server will introspect into the maps and make sure that it can fulfill the users map configuration before starting.

View File

@ -1,19 +1,25 @@
fragLimit: 25
timeLimit: 15m
bot:
minPlayers: 3
game:
motd: "Welcome to Critical Stack"
type: FreeForAll
forceRespawn: false
inactivity: 10m
#password: "letmein"
quadFactor: 3
weaponRespawn: 3
server:
hostname: "quakekube"
maxClients: 12
maxClients: 16
password: "changeme"
commands:
- addbot sarge 2
maps:
- name: q3dm7
type: FreeForAll
timeLimit: 10m
- name: q3dm17
type: FreeForAll
- name: q3wctf1

View File

@ -22,7 +22,7 @@ spec:
- --config=/config/config.yaml
- --content-server=http://localhost:9090
- --agree-eula
image: docker.io/criticalstack/quake:v1.0.3
image: docker.io/criticalstack/quake:v1.0.4
name: server
ports:
- containerPort: 8080
@ -40,7 +40,7 @@ spec:
- q3
- content
- --seed-content-url=http://content.quakejs.com
image: docker.io/criticalstack/quake:v1.0.3
image: docker.io/criticalstack/quake:v1.0.4
name: content-server
ports:
- containerPort: 9090
@ -84,6 +84,8 @@ data:
config.yaml: |
fragLimit: 25
timeLimit: 15m
bot:
minPlayers: 3
game:
motd: "Welcome to Critical Stack"
type: FreeForAll
@ -96,10 +98,11 @@ data:
maxClients: 12
password: "changeme"
commands:
- seta g_inactivity 600
- addbot sarge 2
maps:
- name: q3dm7
type: FreeForAll
timeLimit: 10m
- name: q3dm17
type: FreeForAll
- name: q3wctf1

View File

@ -48,8 +48,19 @@ func NewRouter(cfg *Config) (*echo.Echo, error) {
// default route
e.GET("/", func(c echo.Context) error {
return c.Render(http.StatusOK, "index", map[string]string{
m, err := quakenet.GetInfo(cfg.ServerAddr)
if err != nil {
return err
}
needsPass := false
if v, ok := m["g_needpass"]; ok {
if v == "1" {
needsPass = true
}
}
return c.Render(http.StatusOK, "index", map[string]interface{}{
"ServerAddr": cfg.ServerAddr,
"NeedsPass": needsPass,
})
})

View File

@ -61,6 +61,7 @@ type Config struct {
FragLimit int `name:"fraglimit"`
TimeLimit metav1.Duration `name:"timelimit"`
BotConfig `json:"bot"`
GameConfig `json:"game"`
FileServerConfig `json:"fs"`
ServerConfig `json:"server"`
@ -69,13 +70,20 @@ type Config struct {
Maps
}
type BotConfig struct {
MinPlayers int `name:"bot_minplayers"`
NoChat bool `name:"bot_nochat"`
}
type GameConfig struct {
ForceRespawn bool `name:"g_forcerespawn"`
GameType GameType `json:"type" name:"g_gametype"`
Inactivity metav1.Duration `name:"g_inactivity"`
Log string `name:"g_log"`
MOTD string `name:"g_motd"`
Password string `name:"g_password"`
QuadFactor int `name:"g_quadfactor"`
SinglePlayerSkill int `name:"g_spSkill"`
WeaponRespawn int `name:"g_weaponrespawn"`
}
@ -128,8 +136,6 @@ func writeStruct(v reflect.Value) ([]byte, error) {
data, _ := val.Marshal()
b.Write(data)
case []string:
data := strings.Join(val, "\n")
b.WriteString(fmt.Sprintf("%s\n", data))
default:
panic(fmt.Errorf("received unknown type %T", val))
}
@ -149,6 +155,15 @@ func writeStruct(v reflect.Value) ([]byte, error) {
}
}
}
for i := 0; i < v.Type().NumField(); i++ {
if v.Type().Field(i).Name == "Commands" {
cmds := v.Field(i).Interface().([]string)
for _, cmd := range cmds {
b.WriteString(cmd)
b.WriteString("\n")
}
}
}
return b.Bytes(), nil
}
@ -185,6 +200,9 @@ func Default() *Config {
FragLimit: 25,
TimeLimit: metav1.Duration{Duration: 15 * time.Minute},
Commands: []string{},
BotConfig: BotConfig{
NoChat: true,
},
GameConfig: GameConfig{
Log: "",
MOTD: "Welcome to Critical Stack",
@ -192,6 +210,7 @@ func Default() *Config {
GameType: FreeForAll,
WeaponRespawn: 3,
Inactivity: metav1.Duration{Duration: 10 * time.Minute},
SinglePlayerSkill: 2,
ForceRespawn: false,
},
ServerConfig: ServerConfig{
@ -212,7 +231,7 @@ type Map struct {
CaptureLimit int `json:"captureLimit"`
FragLimit int `json:"fragLimit"`
TimeLimit time.Duration `json:"timeLimit"`
TimeLimit metav1.Duration `json:"timeLimit"`
}
type Maps []Map
@ -229,8 +248,8 @@ func (maps Maps) Marshal() ([]byte, error) {
if m.FragLimit != 0 {
cmds = append(cmds, fmt.Sprintf("fraglimit %d", m.FragLimit))
}
if m.TimeLimit != 0 {
cmds = append(cmds, fmt.Sprintf("timelimit %d", int(m.TimeLimit.Minutes())))
if m.TimeLimit.Duration != 0 {
cmds = append(cmds, fmt.Sprintf("timelimit %s", toString("TimeLimit", reflect.ValueOf(m.TimeLimit))))
}
cmds = append(cmds, fmt.Sprintf("map %s", m.Name))
nextmap := "d0"
@ -240,6 +259,6 @@ func (maps Maps) Marshal() ([]byte, error) {
cmds = append(cmds, fmt.Sprintf("set nextmap vstr %s", nextmap))
b.WriteString(fmt.Sprintf("set d%d \"seta %s\"\n", i, strings.Join(cmds, " ; ")))
}
b.WriteString("vstr d0")
b.WriteString("vstr d0\n")
return b.Bytes(), nil
}

View File

@ -153,7 +153,7 @@ func (s *Server) reload() error {
if err != nil {
return err
}
var cfg *Config
cfg := Default()
if err := yaml.Unmarshal(data, &cfg); err != nil {
return err
}

View File

@ -87,7 +87,7 @@
transform: translate(-50%, -60%);
z-index: 1;
}
.form input[type=text] {
.form input[type=text], [type=password] {
margin: 8px 0;
display: inline-block;
border: 1px solid #ccc;
@ -118,6 +118,9 @@
<div class="centered">
<form class="form">
<input type="text" id="playerName">
{{ with .NeedsPass }}
<input type="password" placeholder="password" id="password">
{{ end }}
<button onclick="join()" type="submit" class="button">Join</button>
</form>
<script type="text/javascript">
@ -137,6 +140,10 @@
args.push.apply(args, ['+set', 'cl_allowDownload', '1'])
args.push.apply(args, ['+name', localStorage.playerName])
args.push.apply(args, getQueryCommands());
var inputPassword = document.getElementById("password");
if (inputPassword && inputPassword.value != "") {
args.push.apply(args, ['+set', 'password', inputPassword.value]);
}
var element = document.getElementById("main");
element.parentNode.removeChild(element);
ioq3.callMain(args);

File diff suppressed because one or more lines are too long