Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

game_world.gno

5.13 Kb · 191 lines
  1package game_world
  2
  3import (
  4	"chain"
  5	"chain/banker"
  6	"chain/runtime"
  7	"chain/runtime/unsafe"
  8	"strconv"
  9
 10	"gno.land/p/akkadia/beta/accesscontrol"
 11	"gno.land/r/akkadia/beta/admin"
 12)
 13
 14const (
 15	CreateGameWorldEvent   = "CreateGameWorld"
 16	RestrictGameWorldEvent = "RestrictGameWorld"
 17)
 18
 19type CreationMode int
 20
 21const (
 22	CreationModeInvalid CreationMode = iota
 23	CreationModeOperatorOnly
 24	CreationModeApprovedCreator
 25	CreationModePublicCreator
 26)
 27
 28type GameWorldStatus int
 29
 30const (
 31	GameWorldStatusInvalid GameWorldStatus = iota
 32	GameWorldStatusExists
 33	GameWorldStatusRestricted
 34)
 35
 36type GameWorld struct {
 37	ID               uint64
 38	Owner            address
 39	GameType         string
 40	Status           GameWorldStatus
 41	CreatedHeight    int64
 42	RestrictedHeight int64
 43}
 44
 45type GameTypePolicy struct {
 46	CreationCost int64
 47	CreationMode CreationMode
 48}
 49
 50func CreateGameWorld(cur realm, gameType string) uint64 {
 51	owner := accesscontrol.MustGetUserCaller(0, cur)
 52	policy := mustGetGameTypePolicy(gameType)
 53	assertCreationAllowed(gameType, owner, policy.CreationMode)
 54	assertExactUgnotPayment(policy.CreationCost)
 55
 56	id := createGameWorld(owner, gameType)
 57	if policy.CreationCost > 0 {
 58		bnk := banker.NewBanker(banker.BankerTypeRealmSend, cur)
 59		bnk.SendCoins(cur.Address(), admin.GetFeeCollector(), chain.Coins{{"ugnot", policy.CreationCost}})
 60	}
 61	emitCreateGameWorld(owner, id, gameType, policy.CreationCost)
 62	return id
 63}
 64
 65func RestrictGameWorld(cur realm, gameWorldID uint64) {
 66	executor := accesscontrol.MustGetAdminOrOperatorCaller(0, cur, admin.IsAdmin, admin.IsOperator)
 67	world := GetGameWorld(gameWorldID)
 68	if world.Status == GameWorldStatusRestricted {
 69		return
 70	}
 71	world.Status = GameWorldStatusRestricted
 72	world.RestrictedHeight = runtime.ChainHeight()
 73	gameWorlds.Set(uint64Key(gameWorldID), world)
 74	removeOwnerGameWorldID(world.Owner, gameWorldID)
 75	restrictedGameWorldIDs.Set(uint64Key(gameWorldID), true)
 76	chain.Emit(
 77		RestrictGameWorldEvent,
 78		"executor", executor.String(),
 79		"id", strconv.FormatUint(gameWorldID, 10),
 80		"oldStatus", strconv.Itoa(int(GameWorldStatusExists)),
 81		"newStatus", strconv.Itoa(int(GameWorldStatusRestricted)),
 82		"restrictedHeight", strconv.FormatInt(world.RestrictedHeight, 10),
 83	)
 84}
 85
 86func HasGameWorld(gameWorldID uint64) bool { return gameWorlds.Has(uint64Key(gameWorldID)) }
 87
 88func GetGameWorld(gameWorldID uint64) GameWorld {
 89	value := gameWorlds.Get(uint64Key(gameWorldID))
 90	if value == nil {
 91		panic("game world not found: " + strconv.FormatUint(gameWorldID, 10))
 92	}
 93	return value.(GameWorld)
 94}
 95
 96func GetLatestGameWorldID() uint64 { return latestGameWorldID }
 97
 98func GetListLimit() int { return gameWorldListLimit }
 99
100func GetRestrictedGameWorldCount() int { return restrictedGameWorldIDs.Size() }
101
102func ListRestrictedGameWorldIDs(page, count int) []uint64 {
103	if page < 1 {
104		panic("page must be at least 1")
105	}
106	if count < 1 || count > gameWorldListLimit {
107		panic("count must be between 1 and 100")
108	}
109	result := []uint64{}
110	restrictedGameWorldIDs.IterateByOffset((page-1)*count, count, func(key string, _ any) bool {
111		result = append(result, parseUint64Key(key))
112		return false
113	})
114	return result
115}
116
117func ListLatestGameWorldsByOwner(owner address) []GameWorld {
118	ids, found := getOwnerGameWorldIDs(owner)
119	if !found {
120		return []GameWorld{}
121	}
122	offset := ids.Size() - gameWorldListLimit
123	if offset < 0 {
124		offset = 0
125	}
126	result := []GameWorld{}
127	ids.IterateByOffset(offset, gameWorldListLimit, func(key string, _ any) bool {
128		result = append(result, GetGameWorld(parseUint64Key(key)))
129		return false
130	})
131	return result
132}
133
134func createGameWorld(owner address, gameType string) uint64 {
135	if latestGameWorldID == uint64(18446744073709551615) {
136		panic("game world id exhausted")
137	}
138	latestGameWorldID++
139	id := latestGameWorldID
140	gameWorlds.Set(uint64Key(id), GameWorld{
141		ID:            id,
142		Owner:         owner,
143		GameType:      gameType,
144		Status:        GameWorldStatusExists,
145		CreatedHeight: runtime.ChainHeight(),
146	})
147	addOwnerGameWorldID(owner, id)
148	return id
149}
150
151func assertCreationAllowed(gameType string, creator address, mode CreationMode) {
152	switch mode {
153	case CreationModeOperatorOnly:
154		if !admin.IsAdmin(creator) && !admin.IsOperator(creator) {
155			panic("game type creation is operator only")
156		}
157	case CreationModeApprovedCreator:
158		if !isApprovedCreator(gameType, creator) {
159			panic("creator is not approved for game type")
160		}
161	case CreationModePublicCreator:
162		return
163	default:
164		panic("invalid creation mode")
165	}
166}
167
168func assertExactUgnotPayment(expected int64) {
169	coins := unsafe.OriginSend()
170	if expected == 0 {
171		if len(coins) != 0 {
172			panic("exact payment required: expected no coins, got " + coins.String())
173		}
174		return
175	}
176	if len(coins) != 1 || coins[0].Denom != "ugnot" || coins[0].Amount != expected {
177		panic("exact payment required: expected " + strconv.FormatInt(expected, 10) + "ugnot, got " + coins.String())
178	}
179}
180
181func emitCreateGameWorld(owner address, id uint64, gameType string, creationCost int64) {
182	chain.Emit(
183		CreateGameWorldEvent,
184		"executor", owner.String(),
185		"id", strconv.FormatUint(id, 10),
186		"owner", owner.String(),
187		"gameType", gameType,
188		"creationCost", strconv.FormatInt(creationCost, 10),
189		"createdHeight", strconv.FormatInt(runtime.ChainHeight(), 10),
190	)
191}