game

package
v0.0.0-...-0db08f9 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 27, 2023 License: GPL-2.0 Imports: 27 Imported by: 0

Documentation

Index

Constants

View Source
const (
	CombatActionAim    = 1
	CombatActionPunch  = 2
	CombatActionStrike = 3
	CombatActionShoot  = 4
	CombatActionDeath  = 5
	CombatActionFlee   = 6
)
View Source
const (
	ItemTypeTrash      = 0
	ItemTypeGun        = 1
	ItemTypeMelee      = 2
	ItemTypeArmor      = 3
	ItemTypeAmmo       = 4
	ItemTypeSmartPhone = 5
	ItemTypeDrug       = 6
	ItemTypeLabel      = 6
	ItemTypeMystery    = 7
)

Variables

View Source
var (
	ItemsList = map[string]*Item{}
	DrugsList = []*Item{}
)
View Source
var BuildingCommandsList = map[string]*Command{
	"/shop": {
		Args:        []string{"name"},
		Description: "Opens the shop's trade menu",
		AllowInGame: true,
		Help: func(c *Client) {
			headings := []string{"Example", "Description"}
			lines := [][]string{
				{"/shop", "Will open the first found shop in the area."},
				{"/shop <name>", "Will open the first sho beginning with the name."},
			}

			c.SendEvent(&responses.Generic{
				Ascii:    true,
				Messages: internal.ToTable(headings, lines),
			})
		},
		Call: func(c *Client, args []string) {
			if c.Player == nil {
				return
			}

			var building *Building
			var altBuilding *Building
			name := ""

			if len(args) > 0 {
				name = strings.ToLower(args[0])
			}

			for _, b := range c.Player.Loc.Buildings {
				if b.ShopStock != nil && len(b.ShopStock) > 0 || b.ShopBuyType != nil && len(b.ShopBuyType) > 0 {
					altBuilding = b

					if strings.HasPrefix(strings.ToLower(b.Name), name) {
						building = b
						break
					}
				}
			}

			if building == nil {
				building = altBuilding

				if building == nil {
					c.SendEvent(&responses.Generic{
						Messages: []string{"There are no shops around here by that name"},
					})
					return
				}
			}

			building.OpenShop(c)
		},
	},
	"/drink": {
		Args:        []string{"amount"},
		Description: "Drink and buy rounds to increase you reputation.",
		Example:     "/drink 10",
		AllowInGame: true,
		Call: func(c *Client, args []string) {
			if len(args) == 0 {
				c.SendEvent(&responses.Generic{
					Messages: []string{"Missing amount. Try: /drink help"},
				})
				return
			}

			amount, err := strconv.ParseInt(args[0], 10, 32)
			if err != nil || amount < 1 {
				c.SendEvent(&responses.Generic{
					Messages: []string{"Invalid amount. Try: /drink help"},
				})
				return
			}

			health_cost := int(amount * settings.DrinkHealthCost)
			drink_cost := int64(amount * settings.DrinkCost)
			rep_gain := int64(amount * settings.DrinkRepGain)

			if c.Player.Cash < drink_cost {
				c.SendEvent(&responses.Generic{
					Messages: []string{"You do not have enough money on you"},
				})
				return
			}

			if c.Player.Health <= health_cost {
				c.SendEvent(&responses.Generic{
					Messages: []string{"Don't be stupid, that will kill you."},
				})
				return
			}

			c.Player.Cash -= drink_cost
			c.Player.Health -= health_cost
			c.Player.Reputation += rep_gain

			c.SendEvent(&responses.Generic{
				Status:   responses.ResponseStatus_RESPONSE_STATUS_INFO,
				Messages: []string{fmt.Sprintf("You spend %d on buying drinks and paying for strippers, your reputation increased by %d", drink_cost, rep_gain)},
			})

			go c.Player.PlayerSendStatsUpdate()
		},
		Help: func(c *Client) {
			headings := []string{"Example", "Health Cost", "$ Cost", "Rep Gain"}
			lines := [][]string{
				{"/drink 1", fmt.Sprintf("%d", settings.DrinkHealthCost), fmt.Sprintf("%d", settings.DrinkCost), fmt.Sprintf("%d", settings.DrinkRepGain)},
				{"/drink 5", fmt.Sprintf("%d", 5*settings.DrinkHealthCost), fmt.Sprintf("%d", 5*settings.DrinkCost), fmt.Sprintf("%d", 5*settings.DrinkRepGain)},
				{"/drink 10", fmt.Sprintf("%d", 10*settings.DrinkHealthCost), fmt.Sprintf("%d", 10*settings.DrinkCost), fmt.Sprintf("%d", 10*settings.DrinkRepGain)},
			}

			c.SendEvent(&responses.Generic{
				Ascii:    true,
				Messages: internal.ToTable(headings, lines),
			})
		},
	},
	"/transfer": {
		Args:        []string{"user", "amount"},
		Description: "Transfer money from your bank account to another.",
		Example:     "/transfer <User> 100",
		AllowInGame: true,
		Call: func(c *Client, args []string) {
			if c.Player.Hometown != c.Player.Loc.City.ShortName {
				c.SendEvent(&responses.Generic{
					Messages: []string{"You can only use the bank to withdraw money when you are not in your home city."},
				})
				return
			}

			username := args[0]
			amount, err := strconv.ParseInt(args[1], 10, 32)
			if err != nil || amount < 1 {
				c.SendEvent(&responses.Generic{
					Messages: []string{"Invalid amount. Try: /transfer <username> <amount>"},
				})
				return
			}

			c.Player.Mu.Lock()
			defer c.Player.Mu.Unlock()

			if amount > c.Player.Bank {
				c.SendEvent(&responses.Generic{
					Messages: []string{"You don't have that much money in your bank account."},
				})
				return
			}

			var player *Entity
			for tc := range c.Game.Clients {
				if tc.Player == nil {
					return
				}

				if strings.ToLower(tc.Player.Name) == username {
					player = tc.Player
					break
				}
			}

			if player == nil {
				c.SendEvent(&responses.Generic{
					Messages: []string{"We cannot find anyone going by that name."},
				})
				return
			}

			player.Mu.Lock()
			defer player.Mu.Unlock()

			c.Player.Bank -= amount
			player.Bank += amount

			c.SendEvent(&responses.Generic{
				Messages: []string{fmt.Sprintf("You just transferred $%d to %s", amount, player.Name)},
			})

			player.Client.SendEvent(&responses.Generic{
				Messages: []string{fmt.Sprintf("%s just transferred you $%d", c.Player.Name, amount)},
			})

			logger.LogMoney(c.Player.Name, "transfer", amount, player.Name)
		},
	},
	"/deposit": {
		Args:        []string{"amount"},
		Description: "Deposit money in your bank account.",
		Example:     "/deposit 100",
		AllowInGame: true,
		Call: func(c *Client, args []string) {
			if c.Player.Hometown != c.Player.Loc.City.ShortName {
				c.SendEvent(&responses.Generic{
					Messages: []string{"You can only use the bank to withdraw money when you are not in your home city."},
				})
				return
			}

			if len(args) == 0 {
				c.SendEvent(&responses.Generic{
					Messages: []string{"Missing amount. Try: /deposit help"},
				})
				return
			}

			amount, err := strconv.ParseInt(args[0], 10, 32)
			if err != nil || amount < 1 {
				c.SendEvent(&responses.Generic{
					Messages: []string{"Invalid amount. Try: /deposit help"},
				})
				return
			}

			if c.Player.Cash < amount {
				c.SendEvent(&responses.Generic{
					Messages: []string{fmt.Sprintf("You do not have %d on you", amount)},
				})
				return
			}

			logger.LogMoney(c.Player.Name, "bank-pre-statement", c.Player.Bank, "")
			c.Player.Cash -= amount
			c.Player.Bank += amount
			logger.LogMoney(c.Player.Name, "deposit", amount, "")
			logger.LogMoney(c.Player.Name, "bank-post-statement", c.Player.Bank, "")

			c.SendEvent(&responses.Generic{
				Status:   responses.ResponseStatus_RESPONSE_STATUS_INFO,
				Messages: []string{fmt.Sprintf("You deposit $%d in your bank account.", amount)},
			})

			go c.Player.PlayerSendStatsUpdate()
		},
		Help: func(c *Client) {
			headings := []string{"Example", ""}
			lines := [][]string{
				{"/deposit 100", "Deposits $100"},
			}

			c.SendEvent(&responses.Generic{
				Ascii:    true,
				Messages: internal.ToTable(headings, lines),
			})
		},
	},
	"/withdraw": {
		Args:        []string{"amount"},
		Description: "Withdraw money from your bank account.",
		Example:     "/withdraw 100",
		AllowInGame: true,
		Call: func(c *Client, args []string) {
			if len(args) == 0 {
				c.SendEvent(&responses.Generic{
					Messages: []string{"Missing amount. Try: /withdraw help"},
				})
				return
			}

			argAmount, err := strconv.ParseInt(args[0], 10, 32)
			if err != nil || argAmount < 1 {
				c.SendEvent(&responses.Generic{
					Messages: []string{"Invalid amount. Try: /withdraw help"},
				})
				return
			}

			amount := argAmount

			if c.Player.Bank < amount {
				c.SendEvent(&responses.Generic{
					Messages: []string{fmt.Sprintf("You do not have $%d in the bank", amount)},
				})
				return
			}

			logger.LogMoney(c.Player.Name, "bank-pre-statement", c.Player.Bank, "")
			logger.LogMoney(c.Player.Name, "withdraw", amount, "")

			c.Player.Bank -= amount
			c.Player.Cash += amount

			c.SendEvent(&responses.Generic{
				Status:   responses.ResponseStatus_RESPONSE_STATUS_INFO,
				Messages: []string{fmt.Sprintf("You withdraw $%d from your account.", amount)},
			})

			logger.LogMoney(c.Player.Name, "bank-post-statement", c.Player.Bank, "")

			go c.Player.PlayerSendStatsUpdate()
		},
		Help: func(c *Client) {
			headings := []string{"Example", ""}
			lines := [][]string{
				{"/withdraw 100", "Withdraws $100"},
			}

			c.SendEvent(&responses.Generic{
				Ascii:    true,
				Messages: internal.ToTable(headings, lines),
			})
		},
	},
	"/heal": {
		Args:        []string{"amount"},
		Description: "You can heal up here.",
		Example:     "/heal 12",
		AllowInGame: true,
		Call: func(c *Client, args []string) {
			if len(args) == 0 {
				c.SendEvent(&responses.Generic{
					Messages: []string{"Missing amount. Try: /heal help"},
				})
				return
			}

			if c.Player.Health >= settings.PlayerMaxHealth {
				c.SendEvent(&responses.Generic{
					Messages: []string{"You do not need any patching up, you are at full health"},
				})
				return
			}

			amount, err := strconv.ParseInt(args[0], 10, 32)
			if err != nil || amount < 1 {
				c.SendEvent(&responses.Generic{
					Messages: []string{"Invalid amount. Try: /heal help"},
				})
				return
			}

			healAmount := int(amount)

			if healAmount > settings.PlayerMaxHealth-c.Player.Health {
				healAmount = settings.PlayerMaxHealth - c.Player.Health
			}

			healCost := int64(healAmount * settings.HealCostPerPoint)

			if c.Player.Cash < healCost {
				c.SendEvent(&responses.Generic{
					Messages: []string{fmt.Sprintf("You do not have enough money. Costs %d per point to heal", settings.HealCostPerPoint)},
				})
				return
			}

			c.Player.Cash -= healCost
			c.Player.Health += healAmount

			if c.Player.Health > settings.PlayerMaxHealth {
				c.Player.Health = settings.PlayerMaxHealth
			}

			c.SendEvent(&responses.Generic{
				Status:   responses.ResponseStatus_RESPONSE_STATUS_INFO,
				Messages: []string{fmt.Sprintf("You pay the doctor %d to patch you up.", healAmount*settings.HealCostPerPoint)},
			})

			go c.Player.PlayerSendStatsUpdate()
		},
		Help: func(c *Client) {
			headings := []string{"Example", "Cost per HP", "Total Cost"}
			lines := [][]string{
				{"/heal 10", fmt.Sprintf("%d", settings.HealCostPerPoint), fmt.Sprintf("%d", 10*settings.HealCostPerPoint)},
				{"/heal 43", fmt.Sprintf("%d", settings.HealCostPerPoint), fmt.Sprintf("%d", 43*settings.HealCostPerPoint)},
			}

			c.SendEvent(&responses.Generic{
				Ascii:    true,
				Messages: internal.ToTable(headings, lines),
			})
		},
	},
	"/travel": {
		Args:        []string{"destination"},
		Description: "travel to another city. /travel help for destinations",
		Example:     "/travel lon",
		AllowInGame: true,
		Call: func(c *Client, args []string) {
			if len(args) == 0 {
				c.SendEvent(&responses.Generic{
					Messages: []string{"Missing destination. Try: /travel help"},
				})
				return
			}

			city, ok := c.Game.World[strings.ToUpper(args[0])]
			if !ok {
				c.SendEvent(&responses.Generic{
					Messages: []string{"Invalid destination. Try: /travel help"},
				})
				return
			}

			if c.Player.Cash < city.TravelCost {
				c.SendEvent(&responses.Generic{
					Messages: []string{"You do not have enough cash on you"},
				})
				return
			}

			c.Player.Cash -= city.TravelCost

			for _, poi := range city.POILocations {
				if poi.POIType == BuildingTypeAirport {
					city.Grid[poi.toString()].PlayerJoin <- c

					c.SendEvent(&responses.Generic{
						Status:   responses.ResponseStatus_RESPONSE_STATUS_INFO,
						Messages: []string{"You fly off to your destination"},
					})

					go c.Player.PlayerSendStatsUpdate()
				}
			}
		},
		Help: func(c *Client) {
			headings := []string{"Destination", "Cost", "Command"}
			lines := [][]string{}

			for _, city := range c.Game.World {
				lines = append(lines, []string{
					city.Name,
					fmt.Sprintf("%d", city.TravelCost),
					fmt.Sprintf("/travel %s", city.ShortName),
				})
			}

			c.SendEvent(&responses.Generic{
				Ascii:    true,
				Messages: internal.ToTable(headings, lines),
			})
		},
	},
}
View Source
var BuildingTemplates = map[BuildingType]BuildingTemplate{
	BuildingTypeAirport: {
		Name: "International Airport",
		Commands: []string{
			"/travel",
		},
	},
	BuildingTypeHospital: {
		Name: "Private Hospital",
		Commands: []string{
			"/heal",
		},
	},
	BuildingTypeBank: {
		Name: "City Bank",
		Commands: []string{
			"/withdraw",
			"/deposit",
			"/transfer",
		},
	},
	BuildingTypeBar: {
		Name: "Old Speakeasy",
		Commands: []string{
			"/drink",
		},
	},
	BuildingTypePawnShop: {
		Name:         "Pawn Shop",
		MerchantType: responses.MerchantType_MERCHANT_PAWN_SHOP,
		Commands: []string{
			"/shop",
		},
		ShopStock: map[string]int32{
			"smartphone": -1,
		},
		ShopBuyType: map[IType]int32{
			ItemTypeMelee:      -1,
			ItemTypeSmartPhone: -1,
			ItemTypeTrash:      -1,
		},
	},
	BuildingTypeArms: {
		Name:         "Arms Dealer",
		MerchantType: responses.MerchantType_MERCHANT_ARMS_DEALER,
		Commands: []string{
			"/shop",
		},
		ShopStock: map[string]int32{
			"iia_armor":    -1,
			"ii_armor":     -1,
			"iiia_armor":   -1,
			"iii_armor":    -1,
			"iv_armor":     -1,
			"stabvest":     -1,
			"chainmail":    -1,
			"hardarmor":    -1,
			"subsonic":     -1,
			"sdammo":       -1,
			"plusp":        -1,
			"pluspplus":    -1,
			"apammo":       -1,
			"beretta92":    -1,
			"glock22":      -1,
			"sigp320":      -1,
			"sw610":        -1,
			"1911":         -1,
			"ragingbull":   -1,
			"ar-15":        -1,
			"ak47":         -1,
			"scarh":        -1,
			"m82":          -1,
			"brassknuckle": -1,
			"pipewrench":   -1,
			"crowbar":      -1,
			"switchblade":  -1,
			"bbbat":        -1,
			"fireaxe":      -1,
			"machete":      -1,
			"katana":       -1,
			"chainsaw":     -1,
		},
		ShopBuyType: map[IType]int32{
			ItemTypeGun:        -1,
			ItemTypeAmmo:       -1,
			ItemTypeArmor:      -1,
			ItemTypeMelee:      -1,
			ItemTypeSmartPhone: -1,
		},
	},
}
View Source
var CityTemplates = []CityTemplate{
	{
		Name:          "Beijing",
		ShortName:     "BJ",
		Width:         30,
		Height:        30,
		TravelCostMin: 800,
		TravelCostMax: 1200,
		NpcSpawnList: map[NPCType]uint8{
			DrugDealer:       2,
			DrugAddict:       2,
			Homeless:         6,
			Tweaker:          2,
			Bouncer:          2,
			Busker:           3,
			StreetVendor:     4,
			StreetGangMember: 2,
			Tourist:          5,
			Activist:         5,
			PoliceOfficer:    3,
			BeatCop:          2,
			DeliveryDriver:   4,
		},
		BuildingLocations: []BuildingLocation{
			{
				Coords: Coordinates{
					North: 29,
					East:  20,
				},
				Buildings: []BuildingType{
					BuildingTypePawnShop,
				},
			},
			{
				Coords: Coordinates{
					North: 12,
					East:  4,
				},
				Buildings: []BuildingType{
					BuildingTypeAirport,
				},
			},
			{
				Coords: Coordinates{
					North: 29,
					East:  21,
				},
				Buildings: []BuildingType{
					BuildingTypeHospital,
				},
			},
			{
				Coords: Coordinates{
					North: 6,
					East:  17,
				},
				Buildings: []BuildingType{
					BuildingTypeArms,
				},
			},
			{
				Coords: Coordinates{
					North: 25,
					East:  10,
				},
				Buildings: []BuildingType{
					BuildingTypeBank,
				},
			},
			{
				Coords: Coordinates{
					North: 8,
					East:  30,
				},
				Buildings: []BuildingType{
					BuildingTypeBar,
				},
			},
		},
	},
	{
		Name:          "Tokyo",
		ShortName:     "TY",
		Width:         30,
		Height:        30,
		TravelCostMin: 800,
		TravelCostMax: 1200,
		NpcSpawnList: map[NPCType]uint8{
			DrugDealer:       2,
			DrugAddict:       2,
			Homeless:         6,
			Tweaker:          2,
			Bouncer:          2,
			Busker:           3,
			StreetVendor:     4,
			StreetGangMember: 2,
			Tourist:          5,
			Activist:         5,
			PoliceOfficer:    3,
			BeatCop:          2,
			DeliveryDriver:   4,
		},
		BuildingLocations: []BuildingLocation{
			{
				Coords: Coordinates{
					North: 28,
					East:  4,
				},
				Buildings: []BuildingType{
					BuildingTypePawnShop,
				},
			},
			{
				Coords: Coordinates{
					North: 12,
					East:  27,
				},
				Buildings: []BuildingType{
					BuildingTypeAirport,
				},
			},
			{
				Coords: Coordinates{
					North: 18,
					East:  8,
				},
				Buildings: []BuildingType{
					BuildingTypeHospital,
				},
			},
			{
				Coords: Coordinates{
					North: 24,
					East:  5,
				},
				Buildings: []BuildingType{
					BuildingTypeArms,
				},
			},
			{
				Coords: Coordinates{
					North: 6,
					East:  15,
				},
				Buildings: []BuildingType{
					BuildingTypeBank,
				},
			},
			{
				Coords: Coordinates{
					North: 9,
					East:  22,
				},
				Buildings: []BuildingType{
					BuildingTypeBar,
				},
			},
		},
	},
	{
		Name:          "Moscow",
		ShortName:     "MC",
		Width:         30,
		Height:        30,
		TravelCostMin: 240,
		TravelCostMax: 600,
		NpcSpawnList: map[NPCType]uint8{
			DrugDealer:       2,
			DrugAddict:       2,
			Homeless:         6,
			Tweaker:          2,
			Bouncer:          2,
			Busker:           3,
			StreetVendor:     4,
			StreetGangMember: 2,
			Tourist:          5,
			Activist:         5,
			PoliceOfficer:    3,
			BeatCop:          2,
			DeliveryDriver:   4,
		},
		BuildingLocations: []BuildingLocation{
			{
				Coords: Coordinates{
					North: 3,
					East:  29,
				},
				Buildings: []BuildingType{
					BuildingTypePawnShop,
				},
			},
			{
				Coords: Coordinates{
					North: 16,
					East:  11,
				},
				Buildings: []BuildingType{
					BuildingTypeAirport,
				},
			},
			{
				Coords: Coordinates{
					North: 5,
					East:  19,
				},
				Buildings: []BuildingType{
					BuildingTypeHospital,
				},
			},
			{
				Coords: Coordinates{
					North: 23,
					East:  1,
				},
				Buildings: []BuildingType{
					BuildingTypeArms,
				},
			},
			{
				Coords: Coordinates{
					North: 7,
					East:  21,
				},
				Buildings: []BuildingType{
					BuildingTypeBank,
				},
			},
			{
				Coords: Coordinates{
					North: 28,
					East:  15,
				},
				Buildings: []BuildingType{
					BuildingTypeBar,
				},
			},
		},
	},
	{
		Name:          "Jakata",
		ShortName:     "JK",
		Width:         30,
		Height:        30,
		TravelCostMin: 900,
		TravelCostMax: 1300,
		NpcSpawnList: map[NPCType]uint8{
			DrugDealer:       2,
			DrugAddict:       2,
			Homeless:         6,
			Tweaker:          2,
			Bouncer:          2,
			Busker:           3,
			StreetVendor:     4,
			StreetGangMember: 2,
			Tourist:          5,
			Activist:         5,
			PoliceOfficer:    3,
			BeatCop:          2,
			DeliveryDriver:   4,
		},
		BuildingLocations: []BuildingLocation{
			{
				Coords: Coordinates{
					North: 20,
					East:  12,
				},
				Buildings: []BuildingType{
					BuildingTypePawnShop,
				},
			},
			{
				Coords: Coordinates{
					North: 12,
					East:  27,
				},
				Buildings: []BuildingType{
					BuildingTypeAirport,
				},
			},
			{
				Coords: Coordinates{
					North: 8,
					East:  18,
				},
				Buildings: []BuildingType{
					BuildingTypeHospital,
				},
			},
			{
				Coords: Coordinates{
					North: 29,
					East:  3,
				},
				Buildings: []BuildingType{
					BuildingTypeArms,
				},
			},
			{
				Coords: Coordinates{
					North: 14,
					East:  21,
				},
				Buildings: []BuildingType{
					BuildingTypeBank,
				},
			},
			{
				Coords: Coordinates{
					North: 4,
					East:  7,
				},
				Buildings: []BuildingType{
					BuildingTypeBar,
				},
			},
		},
	},
	{
		Name:          "Mexico City",
		ShortName:     "MX",
		Width:         30,
		Height:        30,
		TravelCostMin: 300,
		TravelCostMax: 600,
		NpcSpawnList: map[NPCType]uint8{
			DrugDealer:       2,
			DrugAddict:       2,
			Homeless:         6,
			Tweaker:          2,
			Bouncer:          2,
			Busker:           3,
			StreetVendor:     4,
			StreetGangMember: 2,
			Tourist:          5,
			Activist:         5,
			PoliceOfficer:    3,
			BeatCop:          2,
			DeliveryDriver:   4,
		},
		BuildingLocations: []BuildingLocation{
			{
				Coords: Coordinates{
					North: 5,
					East:  18,
				},
				Buildings: []BuildingType{
					BuildingTypePawnShop,
				},
			},
			{
				Coords: Coordinates{
					North: 14,
					East:  27,
				},
				Buildings: []BuildingType{
					BuildingTypeAirport,
				},
			},
			{
				Coords: Coordinates{
					North: 9,
					East:  8,
				},
				Buildings: []BuildingType{
					BuildingTypeHospital,
				},
			},
			{
				Coords: Coordinates{
					North: 20,
					East:  4,
				},
				Buildings: []BuildingType{
					BuildingTypeArms,
				},
			},
			{
				Coords: Coordinates{
					North: 2,
					East:  25,
				},
				Buildings: []BuildingType{
					BuildingTypeBank,
				},
			},
			{
				Coords: Coordinates{
					North: 15,
					East:  18,
				},
				Buildings: []BuildingType{
					BuildingTypeBar,
				},
			},
		},
	},
	{
		Name:          "London",
		ShortName:     "LD",
		Width:         30,
		Height:        30,
		TravelCostMin: 600,
		TravelCostMax: 900,
		NpcSpawnList: map[NPCType]uint8{
			DrugDealer:       2,
			DrugAddict:       2,
			Homeless:         6,
			Tweaker:          2,
			Bouncer:          2,
			Busker:           3,
			StreetVendor:     4,
			StreetGangMember: 2,
			Tourist:          5,
			Activist:         5,
			PoliceOfficer:    3,
			BeatCop:          2,
			DeliveryDriver:   4,
		},
		BuildingLocations: []BuildingLocation{
			{
				Coords: Coordinates{
					North: 1,
					East:  24,
				},
				Buildings: []BuildingType{
					BuildingTypePawnShop,
				},
			},
			{
				Coords: Coordinates{
					North: 23,
					East:  17,
				},
				Buildings: []BuildingType{
					BuildingTypeAirport,
				},
			},
			{
				Coords: Coordinates{
					North: 10,
					East:  6,
				},
				Buildings: []BuildingType{
					BuildingTypeHospital,
				},
			},
			{
				Coords: Coordinates{
					North: 29,
					East:  14,
				},
				Buildings: []BuildingType{
					BuildingTypeArms,
				},
			},
			{
				Coords: Coordinates{
					North: 7,
					East:  29,
				},
				Buildings: []BuildingType{
					BuildingTypeBank,
				},
			},
			{
				Coords: Coordinates{
					North: 25,
					East:  19,
				},
				Buildings: []BuildingType{
					BuildingTypeBar,
				},
			},
		},
	},
	{
		Name:          "Berlin",
		ShortName:     "BL",
		Width:         30,
		Height:        30,
		TravelCostMin: 200,
		TravelCostMax: 400,
		NpcSpawnList: map[NPCType]uint8{
			DrugDealer:       2,
			DrugAddict:       2,
			Homeless:         6,
			Tweaker:          2,
			Bouncer:          2,
			Busker:           3,
			StreetVendor:     4,
			StreetGangMember: 2,
			Tourist:          5,
			Activist:         5,
			PoliceOfficer:    3,
			BeatCop:          2,
			DeliveryDriver:   4,
		},
		BuildingLocations: []BuildingLocation{
			{
				Coords: Coordinates{
					North: 14,
					East:  26,
				},
				Buildings: []BuildingType{
					BuildingTypePawnShop,
				},
			},
			{
				Coords: Coordinates{
					North: 23,
					East:  10,
				},
				Buildings: []BuildingType{
					BuildingTypeAirport,
				},
			},
			{
				Coords: Coordinates{
					North: 12,
					East:  26,
				},
				Buildings: []BuildingType{
					BuildingTypeHospital,
				},
			},
			{
				Coords: Coordinates{
					North: 5,
					East:  19,
				},
				Buildings: []BuildingType{
					BuildingTypeArms,
				},
			},
			{
				Coords: Coordinates{
					North: 30,
					East:  7,
				},
				Buildings: []BuildingType{
					BuildingTypeBank,
				},
			},
			{
				Coords: Coordinates{
					North: 8,
					East:  29,
				},
				Buildings: []BuildingType{
					BuildingTypeBar,
				},
			},
		},
	},
	{
		Name:          "Madrid",
		ShortName:     "MD",
		Width:         30,
		Height:        30,
		TravelCostMin: 600,
		TravelCostMax: 900,
		NpcSpawnList: map[NPCType]uint8{
			DrugDealer:       2,
			DrugAddict:       2,
			Homeless:         6,
			Tweaker:          2,
			Bouncer:          2,
			Busker:           3,
			StreetVendor:     4,
			StreetGangMember: 2,
			Tourist:          5,
			Activist:         5,
			PoliceOfficer:    3,
			BeatCop:          2,
			DeliveryDriver:   4,
		},
		BuildingLocations: []BuildingLocation{
			{
				Coords: Coordinates{
					North: 14,
					East:  8,
				},
				Buildings: []BuildingType{
					BuildingTypePawnShop,
				},
			},
			{
				Coords: Coordinates{
					North: 12,
					East:  22,
				},
				Buildings: []BuildingType{
					BuildingTypeAirport,
				},
			},
			{
				Coords: Coordinates{
					North: 5,
					East:  17,
				},
				Buildings: []BuildingType{
					BuildingTypeHospital,
				},
			},
			{
				Coords: Coordinates{
					North: 30,
					East:  3,
				},
				Buildings: []BuildingType{
					BuildingTypeArms,
				},
			},
			{
				Coords: Coordinates{
					North: 7,
					East:  10,
				},
				Buildings: []BuildingType{
					BuildingTypeBank,
				},
			},
			{
				Coords: Coordinates{
					North: 14,
					East:  9,
				},
				Buildings: []BuildingType{
					BuildingTypeBar,
				},
			},
		},
	},
	{
		Name:          "Pretoria",
		ShortName:     "PT",
		Width:         30,
		Height:        30,
		TravelCostMin: 1200,
		TravelCostMax: 1800,
		NpcSpawnList: map[NPCType]uint8{
			DrugDealer:       2,
			DrugAddict:       2,
			Homeless:         6,
			Tweaker:          2,
			Bouncer:          2,
			Busker:           3,
			StreetVendor:     4,
			StreetGangMember: 2,
			Tourist:          5,
			Activist:         5,
			PoliceOfficer:    3,
			BeatCop:          2,
			DeliveryDriver:   4,
		},
		BuildingLocations: []BuildingLocation{
			{
				Coords: Coordinates{
					North: 24,
					East:  3,
				},
				Buildings: []BuildingType{
					BuildingTypePawnShop,
				},
			},
			{
				Coords: Coordinates{
					North: 17,
					East:  12,
				},
				Buildings: []BuildingType{
					BuildingTypeAirport,
				},
			},
			{
				Coords: Coordinates{
					North: 28,
					East:  30,
				},
				Buildings: []BuildingType{
					BuildingTypeHospital,
				},
			},
			{
				Coords: Coordinates{
					North: 6,
					East:  3,
				},
				Buildings: []BuildingType{
					BuildingTypeArms,
				},
			},
			{
				Coords: Coordinates{
					North: 19,
					East:  21,
				},
				Buildings: []BuildingType{
					BuildingTypeBank,
				},
			},
			{
				Coords: Coordinates{
					North: 10,
					East:  29,
				},
				Buildings: []BuildingType{
					BuildingTypeBar,
				},
			},
		},
	},
	{
		Name:          "Rome",
		ShortName:     "RO",
		Width:         30,
		Height:        30,
		TravelCostMin: 200,
		TravelCostMax: 400,
		NpcSpawnList: map[NPCType]uint8{
			DrugDealer:       2,
			DrugAddict:       2,
			Homeless:         6,
			Tweaker:          2,
			Bouncer:          2,
			Busker:           3,
			StreetVendor:     4,
			StreetGangMember: 2,
			Tourist:          5,
			Activist:         5,
			PoliceOfficer:    3,
			BeatCop:          2,
			DeliveryDriver:   4,
		},
		BuildingLocations: []BuildingLocation{
			{
				Coords: Coordinates{
					North: 21,
					East:  18,
				},
				Buildings: []BuildingType{
					BuildingTypePawnShop,
				},
			},
			{
				Coords: Coordinates{
					North: 22,
					East:  5,
				},
				Buildings: []BuildingType{
					BuildingTypeAirport,
				},
			},
			{
				Coords: Coordinates{
					North: 13,
					East:  18,
				},
				Buildings: []BuildingType{
					BuildingTypeHospital,
				},
			},
			{
				Coords: Coordinates{
					North: 7,
					East:  26,
				},
				Buildings: []BuildingType{
					BuildingTypeArms,
				},
			},
			{
				Coords: Coordinates{
					North: 30,
					East:  2,
				},
				Buildings: []BuildingType{
					BuildingTypeBank,
				},
			},
			{
				Coords: Coordinates{
					North: 25,
					East:  14,
				},
				Buildings: []BuildingType{
					BuildingTypeBar,
				},
			},
		},
	},
	{
		Name:          "Paris",
		ShortName:     "PA",
		Width:         30,
		Height:        30,
		TravelCostMin: 400,
		TravelCostMax: 900,
		NpcSpawnList: map[NPCType]uint8{
			DrugDealer:       2,
			DrugAddict:       2,
			Homeless:         6,
			Tweaker:          2,
			Bouncer:          2,
			Busker:           3,
			StreetVendor:     4,
			StreetGangMember: 2,
			Tourist:          5,
			Activist:         5,
			PoliceOfficer:    3,
			BeatCop:          2,
			DeliveryDriver:   4,
		},
		BuildingLocations: []BuildingLocation{
			{
				Coords: Coordinates{
					North: 19,
					East:  12,
				},
				Buildings: []BuildingType{
					BuildingTypePawnShop,
				},
			},
			{
				Coords: Coordinates{
					North: 8,
					East:  27,
				},
				Buildings: []BuildingType{
					BuildingTypeAirport,
				},
			},
			{
				Coords: Coordinates{
					North: 16,
					East:  10,
				},
				Buildings: []BuildingType{
					BuildingTypeHospital,
				},
			},
			{
				Coords: Coordinates{
					North: 30,
					East:  15,
				},
				Buildings: []BuildingType{
					BuildingTypeArms,
				},
			},
			{
				Coords: Coordinates{
					North: 4,
					East:  3,
				},
				Buildings: []BuildingType{
					BuildingTypeBank,
				},
			},
			{
				Coords: Coordinates{
					North: 19,
					East:  22,
				},
				Buildings: []BuildingType{
					BuildingTypeBar,
				},
			},
		},
	},
	{
		Name:          "Warsaw",
		ShortName:     "WS",
		Width:         30,
		Height:        30,
		TravelCostMin: 200,
		TravelCostMax: 400,
		NpcSpawnList: map[NPCType]uint8{
			DrugDealer:       2,
			DrugAddict:       2,
			Homeless:         6,
			Tweaker:          2,
			Bouncer:          2,
			Busker:           3,
			StreetVendor:     4,
			StreetGangMember: 2,
			Tourist:          5,
			Activist:         5,
			PoliceOfficer:    3,
			BeatCop:          2,
			DeliveryDriver:   4,
		},
		BuildingLocations: []BuildingLocation{
			{
				Coords: Coordinates{
					North: 24,
					East:  7,
				},
				Buildings: []BuildingType{
					BuildingTypePawnShop,
				},
			},
			{
				Coords: Coordinates{
					North: 11,
					East:  20,
				},
				Buildings: []BuildingType{
					BuildingTypeAirport,
				},
			},
			{
				Coords: Coordinates{
					North: 5,
					East:  9,
				},
				Buildings: []BuildingType{
					BuildingTypeHospital,
				},
			},
			{
				Coords: Coordinates{
					North: 28,
					East:  6,
				},
				Buildings: []BuildingType{
					BuildingTypeArms,
				},
			},
			{
				Coords: Coordinates{
					North: 23,
					East:  12,
				},
				Buildings: []BuildingType{
					BuildingTypeBank,
				},
			},
			{
				Coords: Coordinates{
					North: 2,
					East:  29,
				},
				Buildings: []BuildingType{
					BuildingTypeBar,
				},
			},
		},
	},
	{
		Name:          "Stockholm",
		ShortName:     "SH",
		Width:         30,
		Height:        30,
		TravelCostMin: 200,
		TravelCostMax: 400,
		NpcSpawnList: map[NPCType]uint8{
			DrugDealer:       2,
			DrugAddict:       2,
			Homeless:         6,
			Tweaker:          2,
			Bouncer:          2,
			Busker:           3,
			StreetVendor:     4,
			StreetGangMember: 2,
			Tourist:          5,
			Activist:         5,
			PoliceOfficer:    3,
			BeatCop:          2,
			DeliveryDriver:   4,
		},
		BuildingLocations: []BuildingLocation{
			{
				Coords: Coordinates{
					North: 27,
					East:  12,
				},
				Buildings: []BuildingType{
					BuildingTypePawnShop,
				},
			},
			{
				Coords: Coordinates{
					North: 18,
					East:  7,
				},
				Buildings: []BuildingType{
					BuildingTypeAirport,
				},
			},
			{
				Coords: Coordinates{
					North: 1,
					East:  30,
				},
				Buildings: []BuildingType{
					BuildingTypeHospital,
				},
			},
			{
				Coords: Coordinates{
					North: 14,
					East:  23,
				},
				Buildings: []BuildingType{
					BuildingTypeArms,
				},
			},
			{
				Coords: Coordinates{
					North: 10,
					East:  19,
				},
				Buildings: []BuildingType{
					BuildingTypeBank,
				},
			},
			{
				Coords: Coordinates{
					North: 26,
					East:  2,
				},
				Buildings: []BuildingType{
					BuildingTypeBar,
				},
			},
		},
	},
	{
		Name:          "New York City",
		ShortName:     "NY",
		Width:         30,
		Height:        30,
		TravelCostMin: 500,
		TravelCostMax: 900,
		NpcSpawnList: map[NPCType]uint8{
			DrugDealer:       2,
			DrugAddict:       2,
			Homeless:         6,
			Tweaker:          2,
			Bouncer:          2,
			Busker:           3,
			StreetVendor:     4,
			StreetGangMember: 2,
			Tourist:          5,
			Activist:         5,
			PoliceOfficer:    3,
			BeatCop:          2,
			DeliveryDriver:   4,
		},
		BuildingLocations: []BuildingLocation{
			{
				Coords: Coordinates{
					North: 25,
					East:  19,
				},
				Buildings: []BuildingType{
					BuildingTypePawnShop,
				},
			},
			{
				Coords: Coordinates{
					North: 9,
					East:  16,
				},
				Buildings: []BuildingType{
					BuildingTypeAirport,
				},
			},
			{
				Coords: Coordinates{
					North: 21,
					East:  8,
				},
				Buildings: []BuildingType{
					BuildingTypeHospital,
				},
			},
			{
				Coords: Coordinates{
					North: 3,
					East:  27,
				},
				Buildings: []BuildingType{
					BuildingTypeArms,
				},
			},
			{
				Coords: Coordinates{
					North: 24,
					East:  13,
				},
				Buildings: []BuildingType{
					BuildingTypeBank,
				},
			},
			{
				Coords: Coordinates{
					North: 15,
					East:  12,
				},
				Buildings: []BuildingType{
					BuildingTypeBar,
				},
			},
		},
	},
	{
		Name:          "Las Vegas",
		ShortName:     "LV",
		Width:         30,
		Height:        30,
		TravelCostMin: 300,
		TravelCostMax: 600,
		NpcSpawnList: map[NPCType]uint8{
			DrugDealer:       2,
			DrugAddict:       2,
			Homeless:         6,
			Tweaker:          2,
			Bouncer:          2,
			Busker:           3,
			StreetVendor:     4,
			StreetGangMember: 2,
			Tourist:          5,
			Activist:         5,
			PoliceOfficer:    3,
			BeatCop:          2,
			DeliveryDriver:   4,
		},
		BuildingLocations: []BuildingLocation{
			{
				Coords: Coordinates{
					North: 30,
					East:  13,
				},
				Buildings: []BuildingType{
					BuildingTypePawnShop,
				},
			},
			{
				Coords: Coordinates{
					North: 12,
					East:  25,
				},
				Buildings: []BuildingType{
					BuildingTypeAirport,
				},
			},
			{
				Coords: Coordinates{
					North: 17,
					East:  11,
				},
				Buildings: []BuildingType{
					BuildingTypeHospital,
				},
			},
			{
				Coords: Coordinates{
					North: 29,
					East:  6,
				},
				Buildings: []BuildingType{
					BuildingTypeArms,
				},
			},
			{
				Coords: Coordinates{
					North: 4,
					East:  21,
				},
				Buildings: []BuildingType{
					BuildingTypeBank,
				},
			},
			{
				Coords: Coordinates{
					North: 7,
					East:  14,
				},
				Buildings: []BuildingType{
					BuildingTypeBar,
				},
			},
		},
	},
}
View Source
var CommandAliases = map[string]string{
	"/get": "/pickup",
	"/p":   "/pickup",
	"/w":   "/pm",
	"/h":   "/help",
	"/s":   "/say",
	"/g":   "/global",
	"/m":   "/move",
	"/r":   "/refresh",
	"/b":   "/buy",
}
View Source
var CommandsList = map[string]*Command{

	"/use": {
		Args:        []string{"ID"},
		Description: "Uses a given item by ID",
		AllowInGame: true,
		Help: func(c *Client) {
		},
		Call: func(c *Client, args []string) {
			if c.Player.Loc == nil {
				return
			}

			if len(args) != 1 {
				c.SendEvent(&responses.Generic{
					Messages: []string{"Invalid command "},
				})
				return
			}

			c.Player.Inventory.useById(args[0])
		},
	},
	"/shopsell": {
		Args:        []string{"merchantID", "inventoryIndex"},
		Description: "Sells the item at the given inventory index to the given merchant(id)",
		AllowInGame: true,
		Help: func(c *Client) {
		},
		Call: func(c *Client, args []string) {
			if c.Player.Loc == nil {
				return
			}

			if len(args) < 2 {
				c.SendEvent(&responses.Generic{
					Messages: []string{"Invalid command, try /shop <id> <inventory index>"},
				})
				return
			}

			buildingId := args[0]
			index, err := strconv.ParseInt(args[1], 10, 32)
			if err != nil {
				c.SendEvent(&responses.Generic{
					Messages: []string{"Invalid sell index."},
				})
				return
			}

			var building *Building
			for _, b := range c.Player.Loc.Buildings {
				if b.ID == buildingId {
					building = b
				}
			}

			if building == nil {
				c.SendEvent(&responses.Generic{
					Messages: []string{"Invalid building."},
				})
				return
			}

			item := c.Player.Inventory.Items[index]
			if item == nil {
				return
			}

			building.Mu.Lock()
			defer building.Mu.Unlock()

			amount, ok := building.ShopBuyType[item.GetItemType()]
			if !ok {
				c.SendEvent(&responses.MerchantMessage{
					Status:  responses.ResponseStatus_RESPONSE_STATUS_ERROR,
					Message: "I am not interested in that..",
				})
				return
			}

			if amount == 0 {
				c.SendEvent(&responses.MerchantMessage{
					Status:  responses.ResponseStatus_RESPONSE_STATUS_ERROR,
					Message: "I am not looking to buy more of that type of item.",
				})
				return
			}

			itemEvent := c.Player.Inventory.drop(int(index))

			if itemEvent == nil {
				return
			}

			money := int64(float32(itemEvent.Item.GetPrice()) * settings.ItemSellPriceLoss)

			if money == 0 {
				money = 1
			}

			c.Player.Mu.Lock()
			c.Player.Cash += money
			c.Player.Mu.Unlock()

			if amount != -1 {
				building.ShopBuyType[itemEvent.Item.GetItemType()] -= 1
			}

			logger.LogBuySell(c.Player.Name, "sell", money, itemEvent.Item.TemplateName)

			building.SyncShopInventory(c)
			c.Player.PlayerSendInventoryUpdate()
			c.Player.PlayerSendStatsUpdate()

			c.SendEvent(&responses.MerchantMessage{
				Status:  responses.ResponseStatus_RESPONSE_STATUS_SUCCESS,
				Message: fmt.Sprintf("Here's $%d for the %s", money, itemEvent.Item.GetName()),
			})
		},
	},
	"/shopbuy": {
		Args:        []string{"shop type", "inventory index"},
		Description: "Buys the item at the given inventory index from the given shop",
		AllowInGame: true,
		Help: func(c *Client) {
		},
		Call: func(c *Client, args []string) {
			if len(args) < 2 {
				c.SendEvent(&responses.Generic{
					Messages: []string{"Invalid command, try /shop <id> <inventory index>"},
				})
				return
			}

			buildingId := args[0]
			index, err := strconv.ParseInt(args[1], 10, 32)
			if err != nil {
				c.SendEvent(&responses.Generic{
					Messages: []string{"Invalid sell index."},
				})
				return
			}

			var building *Building
			for _, b := range c.Player.Loc.Buildings {
				if b.ID == buildingId {
					building = b
				}
			}

			if building == nil {
				c.SendEvent(&responses.Generic{
					Messages: []string{"Invalid building."},
				})
				return
			}

			if building.ShopStock == nil || len(building.ShopStock) <= 0 {
				c.SendEvent(&responses.Generic{
					Messages: []string{"Invalid shop."},
				})
				return
			}

			if index < 0 || int(index) >= len(building.ShopStock) {
				c.SendEvent(&responses.Generic{
					Messages: []string{"Invalid index."},
				})
				return
			}

			itemToBuy := building.ShopStock[index]

			if itemToBuy.Amount == 0 {
				c.SendEvent(&responses.MerchantMessage{
					Status:  responses.ResponseStatus_RESPONSE_STATUS_ERROR,
					Message: "I have none of those left, pick something else.",
				})
				return
			}

			itemTemplate := ItemsList[itemToBuy.TemplateId]

			if itemTemplate.GetMinRep() > c.Player.Reputation {
				c.SendEvent(&responses.MerchantMessage{
					Status:  responses.ResponseStatus_RESPONSE_STATUS_ERROR,
					Message: "I don't know you well enough to sell you that. Come back when your name is more known.",
				})
				return
			}

			c.Player.Mu.Lock()
			defer c.Player.Mu.Unlock()

			if int64(itemTemplate.GetPrice()) > c.Player.Cash {
				c.SendEvent(&responses.MerchantMessage{
					Status:  responses.ResponseStatus_RESPONSE_STATUS_ERROR,
					Message: "You do not have enough money on you, come back when you have cash.",
				})
				return
			}

			if !c.Player.Inventory.HasRoom() {
				c.SendEvent(&responses.MerchantMessage{
					Status:  responses.ResponseStatus_RESPONSE_STATUS_ERROR,
					Message: "You don't have enough room to buy that.",
				})
				return
			}

			newItem, ok := NewItem(itemToBuy.TemplateId)
			if !ok {
				c.SendEvent(&responses.MerchantMessage{
					Status:  responses.ResponseStatus_RESPONSE_STATUS_ERROR,
					Message: "I can't seem to find that item.. odd. Come back later (Error)",
				})
				return
			}

			c.Player.Cash -= int64(itemTemplate.GetPrice())
			err = c.Player.Inventory.addItem(newItem)
			if err != nil {
				c.SendEvent(&responses.MerchantMessage{
					Status:  responses.ResponseStatus_RESPONSE_STATUS_ERROR,
					Message: "You don't seem to have room, I've dropped the item on the ground (Inventory error)",
				})

				c.Player.Loc.AddItem <- &ItemMoved{
					Item: newItem,
					By:   building.Name,
				}
				return
			}

			logger.LogBuySell(c.Player.Name, "buy", int64(itemTemplate.GetPrice()), itemTemplate.TemplateName)

			c.SendEvent(&responses.MerchantMessage{
				Status:  responses.ResponseStatus_RESPONSE_STATUS_SUCCESS,
				Message: fmt.Sprintf("Done. Here's your %s.", newItem.GetName()),
			})

			building.SyncShopInventory(c)
			c.Player.PlayerSendInventoryUpdate()
			c.Player.PlayerSendStatsUpdate()
		},
	},
	"/selldrug": {
		Args:        []string{"merchant id", "inventory index"},
		Description: "Sells the item at the given inventory index to the given merchant(id)",
		AllowInGame: true,
		Help: func(c *Client) {
		},
		Call: func(c *Client, args []string) {
			if c.Player == nil {
				return
			}

			if len(args) < 2 {
				return
			}

			var druggie *Entity
			sellerID := args[0]
			index, err := strconv.ParseInt(args[1], 10, 32)
			if err != nil {
				return
			}

			for npc := range c.Player.ShoppingWith {
				if npc.NpcType == DrugAddict && sellerID == npc.NpcID {
					druggie = npc
					break
				}
			}

			if druggie == nil {
				c.SendEvent(&responses.Generic{
					Status:   responses.ResponseStatus_RESPONSE_STATUS_ERROR,
					Messages: []string{"There is no one here to tell to."},
				})
				return
			}

			if isHostile := druggie.NpcHostiles[c.UUID]; isHostile {
				return
			}

			item := c.Player.Inventory.Items[index]
			if item == nil {
				return
			}

			if item.GetItemType() != ItemTypeDrug {
				c.SendEvent(&responses.MerchantMessage{
					Status:  responses.ResponseStatus_RESPONSE_STATUS_ERROR,
					Message: "I am not interested in that..",
				})
				return
			}

			if !druggie.Inventory.HasRoom() {
				c.SendEvent(&responses.MerchantMessage{
					Status:  responses.ResponseStatus_RESPONSE_STATUS_ERROR,
					Message: "I got what I need, go sell your shit to someone else.",
				})
				return
			}

			itemEvent := c.Player.Inventory.drop(int(index))

			if itemEvent == nil {
				return
			}

			price := int64((float32(itemEvent.Item.GetPrice()) * settings.DrugProfitMargin) * c.Player.Loc.City.DrugDemands[item.TemplateName])

			if price <= 0 {
				price = 1
			}

			c.Player.Mu.Lock()
			c.Player.Cash += price
			c.Player.Reputation += settings.DrugRepIncrease
			c.Player.Mu.Unlock()

			logger.LogBuySell(c.Player.Name, "sell", price, itemEvent.Item.TemplateName)

			druggie.Inventory.addItem(itemEvent.Item)
			druggie.SyncDruggieTrade(c)
			c.Player.PlayerSendInventoryUpdate()
			c.Player.PlayerSendStatsUpdate()

			c.SendEvent(&responses.MerchantMessage{
				Status:  responses.ResponseStatus_RESPONSE_STATUS_SUCCESS,
				Message: fmt.Sprintf("Here's $%d for the %s", price, itemEvent.Item.GetName()),
			})
		},
	},
	"/purchase": {
		Args:        []string{"merchant id", "inventory index"},
		Description: "Buys the item at the given inventory index from the given merchant(id)",
		AllowInGame: true,
		Help: func(c *Client) {
		},
		Call: func(c *Client, args []string) {
			if c.Player == nil {
				return
			}

			if len(args) < 2 {
				return
			}

			var dealer *Entity
			sellerID := args[0]
			index, err := strconv.ParseInt(args[1], 10, 32)
			if err != nil {
				return
			}

			for t := range c.Player.Loc.Npcs {
				if t.NpcType == DrugDealer && t.NpcID == sellerID {
					dealer = t
					break
				}
			}

			if isHostile := dealer.NpcHostiles[c.UUID]; isHostile {
				return
			}

			dealer.Inventory.Mu.Lock()
			c.Player.Inventory.Mu.Lock()
			defer dealer.Inventory.Mu.Unlock()
			defer c.Player.Inventory.Mu.Unlock()

			if !c.Player.Inventory.HasRoom() {
				c.SendEvent(&responses.MerchantMessage{
					Status:  responses.ResponseStatus_RESPONSE_STATUS_ERROR,
					Message: "There is no room left in your inventory.",
				})
				return
			}

			item := dealer.Inventory.Items[index]
			if item == nil {
				return
			}

			c.Player.Mu.Lock()

			price := int64(float32(item.GetPrice()) * c.Player.Loc.City.DrugDemands[item.TemplateName])

			if price <= 0 {
				price = 1
			}

			if c.Player.Cash < price {
				c.SendEvent(&responses.MerchantMessage{
					Status:  responses.ResponseStatus_RESPONSE_STATUS_ERROR,
					Message: "You do not have enough cash for that.",
				})
				c.Player.Mu.Unlock()
				return
			}

			logger.LogBuySell(c.Player.Name, "buy", price, item.TemplateName)
			c.Player.Mu.Unlock()

			go func() {
				c.Player.Mu.Lock()
				defer c.Player.Mu.Unlock()

				itemEvent := dealer.Inventory.drop(int(index))

				if itemEvent == nil {
					return
				}

				c.Player.Cash -= price
				c.Player.Inventory.addItem(itemEvent.Item)
				dealer.SyncDealerInventory(c)
				c.Player.PlayerSendInventoryUpdate()
				c.Player.PlayerSendStatsUpdate()

				c.SendEvent(&responses.MerchantMessage{
					Status:  responses.ResponseStatus_RESPONSE_STATUS_SUCCESS,
					Message: fmt.Sprintf("Here is your %s, anything else?", itemEvent.Item.GetName()),
				})
			}()
		},
	},
	"/closetrade": {
		Args:        []string{"name"},
		Description: "close active trade",
		AllowInGame: true,
		Help: func(c *Client) {
		},
		Call: func(c *Client, _ []string) {
			if c.Player == nil {
				return
			}

			c.Player.Mu.Lock()

			for t := range c.Player.ShoppingWith {
				t.Mu.Lock()
				delete(t.ShoppingWith, c.Player)
				delete(c.Player.ShoppingWith, t)
				t.Mu.Unlock()
			}

			c.Player.Mu.Unlock()
		},
	},
	"/sell": {
		Args:        []string{"name"},
		Description: "Opens drug sellng menu",
		AllowInGame: true,
		Help: func(c *Client) {
		},
		Call: func(c *Client, args []string) {
			if c.Player == nil {
				return
			}

			var druggie *Entity
			var altDruggie *Entity
			name := ""

			if len(args) > 0 {
				name = strings.ToLower(args[0])
			}

			for t := range c.Player.Loc.Npcs {
				if t.NpcType == DrugAddict {
					altDruggie = t

					if strings.HasPrefix(strings.ToLower(t.Name), name) {
						druggie = t
						break
					}
				}
			}

			if druggie == nil {
				druggie = altDruggie

				if druggie == nil {
					c.SendEvent(&responses.Generic{
						Messages: []string{"There are no druggies by that name."},
					})
					return
				}
			}

			druggie.OpenDruggieMenu(c)
		},
	},
	"/buy": {
		Args:        []string{"name"},
		Description: "Opens drug dealers buy menu",
		AllowInGame: true,
		Help: func(c *Client) {
		},
		Call: func(c *Client, args []string) {
			if c.Player == nil {
				return
			}

			var dealer *Entity
			var altDealer *Entity
			name := ""

			if len(args) > 0 {
				name = strings.ToLower(args[0])
			}

			for t := range c.Player.Loc.Npcs {
				if t.NpcType == DrugDealer {
					altDealer = t

					if strings.HasPrefix(strings.ToLower(t.Name), name) {
						dealer = t
						break
					}
				}
			}

			if dealer == nil {
				dealer = altDealer

				if dealer == nil {
					c.SendEvent(&responses.Generic{
						Messages: []string{"There are no drug dealers here going by that name."},
					})
					return
				}
			}

			dealer.OpenDrugDealer(c)
		},
	},
	"/unequip": {
		Args:        []string{"itemID"},
		Description: "Unequips the given item",
		AllowInGame: true,
		Help: func(c *Client) {
		},
		Call: func(c *Client, args []string) {
			if c.Player.Loc == nil {
				return
			}

			if len(args) != 1 {
				return
			}

			index, err := strconv.ParseInt(args[0], 10, 32)
			if err != nil || index < 0 {
				return
			}

			c.Player.Inventory.unequip(int(index))
			c.Player.PlayerSendInventoryUpdate()
			c.Player.PlayerSendStatsUpdate()
		},
	},
	"/info": {
		Args:        []string{"itemID"},
		Description: "Gives info on the item",
		AllowInGame: true,
		Help: func(c *Client) {
		},
		Call: func(c *Client, args []string) {
			if c.Player.Loc == nil {
				return
			}

			if len(args) != 1 {
				return
			}

			ID := args[0]

			c.Player.Inventory.Info(ID)
		},
	},
	"/equip": {
		Args:        []string{"itemID"},
		Description: "Equips the given item",
		AllowInGame: true,
		Help: func(c *Client) {
		},
		Call: func(c *Client, args []string) {
			if c.Player.Loc == nil {
				return
			}

			if len(args) != 1 {
				return
			}

			ID := args[0]

			c.Player.Inventory.equip(ID)
			c.Player.PlayerSendInventoryUpdate()
			c.Player.PlayerSendStatsUpdate()
		},
	},
	"/unaim": {
		Args:        []string{},
		Description: "Removes your target lock",
		AllowInGame: true,
		Help: func(c *Client) {
		},
		Call: func(c *Client, _ []string) {
			if c.Player.Loc == nil {
				return
			}

			if c.Player.CurrentTarget == nil {
				return
			}

			c.SendEvent(&responses.Generic{
				Messages: []string{fmt.Sprintf("You stop taking aim at %s", c.Player.CurrentTarget.Name)},
			})

			c.Player.CurrentTarget.Client.SendEvent(&responses.Generic{
				Messages: []string{fmt.Sprintf("%s stops taking aim at you.", c.Player.Name)},
			})

			delete(c.Player.CurrentTarget.TargetedBy, c.Player)
			c.Player.CurrentTarget = nil
		},
	},
	"/aim": {
		Args:        []string{"target"},
		Description: "Sets your for your attacks. The target cannot move away while aim'ed at",
		AllowInGame: true,
		Help: func(c *Client) {
		},
		Call: func(c *Client, args []string) {
			if c.Player.Loc == nil {
				return
			}

			if len(args) == 0 {
				return
			}

			var target *Entity
			name := strings.ToLower(args[0])

			for t := range c.Player.Loc.Players {
				if strings.HasPrefix(strings.ToLower(t.Player.Name), name) {
					target = t.Player
					break
				}
			}

			if target == nil {
				for t := range c.Player.Loc.Npcs {
					if strings.HasPrefix(strings.ToLower(t.Name), name) {
						target = t
						break
					}
				}
			}

			if target == nil {
				c.SendEvent(&responses.Generic{
					Messages: []string{"There are no one here going by that name."},
				})
				return
			}

			action := CombatAction{
				Target:   target,
				Attacker: c.Player,
				Action:   CombatActionAim,
			}
			action.Execute()
		},
	},
	"/punch": {
		Args:        []string{},
		Description: "Throws a punch at the target",
		AllowInGame: true,
		Help: func(c *Client) {
		},
		Call: func(c *Client, _ []string) {
			if c.Player.Loc == nil {
				return
			}

			if c.Player.CurrentTarget == nil {
				c.SendEvent(&responses.Generic{
					Messages: []string{"You have no target, so you throw some punches into the air."},
				})
				return
			}

			action := CombatAction{
				Target:   c.Player.CurrentTarget,
				Attacker: c.Player,
				Action:   CombatActionPunch,
			}
			action.Execute()
		},
	},
	"/strike": {
		Args:        []string{},
		Description: "Strikes the target with your melee weapon",
		AllowInGame: true,
		Help: func(c *Client) {
		},
		Call: func(c *Client, _ []string) {
			if c.Player.Loc == nil {
				return
			}

			if c.Player.CurrentTarget == nil {
				c.SendEvent(&responses.Generic{
					Messages: []string{"You have no target."},
				})
				return
			}

			action := CombatAction{
				Target:   c.Player.CurrentTarget,
				Attacker: c.Player,
				Action:   CombatActionStrike,
			}
			action.Execute()
		},
	},
	"/shoot": {
		Args:        []string{},
		Description: "Shoots at the target with your gun",
		AllowInGame: true,
		Help: func(c *Client) {
		},
		Call: func(c *Client, _ []string) {
			if c.Player.Loc == nil {
				return
			}

			if c.Player.CurrentTarget == nil {
				c.SendEvent(&responses.Generic{
					Messages: []string{"You have no target."},
				})
				return
			}

			action := CombatAction{
				Target:   c.Player.CurrentTarget,
				Attacker: c.Player,
				Action:   CombatActionShoot,
			}
			action.Execute()
		},
	},
	"/autoattack": {
		Args:        []string{},
		Description: "Will toggle auto attack on/off.",
		AllowInGame: true,
		Help: func(c *Client) {
		},
		Call: func(c *Client, _ []string) {
			c.Player.Mu.Lock()
			c.Player.AutoAttackEnabled = !c.Player.AutoAttackEnabled
			c.Player.Mu.Unlock()

			c.SendEvent(&responses.Generic{
				Status: responses.ResponseStatus_RESPONSE_STATUS_INFO,
				Messages: []string{
					"Auto Attack, when enabled, will attack an /aim'ed target with the last type of attack used (default: Punch)",
					fmt.Sprintf("Auto Attack enabled: %v", c.Player.AutoAttackEnabled),
				},
			})
		},
	},
	"/flee": {
		Args:        []string{"direction"},
		Description: "Run away from a fight, in the choosen direction, but you could drop some items in the process",
		AllowInGame: true,
		Help: func(c *Client) {
		},
		Call: func(c *Client, args []string) {
			if c.Player.Loc == nil {
				return
			}

			if len(c.Player.TargetedBy) == 0 {
				c.SendEvent(&responses.Generic{
					Messages: []string{"No one is targeting you, no need to flee"},
				})
				return
			}

			north := c.Player.Loc.Coords.North
			east := c.Player.Loc.Coords.East

			switch strings.ToLower(args[0]) {
			case "up", "w", "north":
				north += 1
			case "down", "s", "south":
				north += -1
			case "left", "a", "west":
				east += -1
			case "right", "d", "east":
				east += 1
			}

			if north < 0 || north > int(c.Player.Loc.City.Height) || east < 0 || east > int(c.Player.Loc.City.Width) {
				c.SendEvent(&responses.Generic{
					Status: responses.ResponseStatus_RESPONSE_STATUS_WARN,
					Messages: []string{
						"You cannot move any further in that direction that direction",
					},
				})
				return
			}

			action := CombatAction{
				Target: c.Player,
				Action: CombatActionFlee,
				Direction: &Coordinates{
					North: c.Player.Loc.Coords.North + north,
					East:  c.Player.Loc.Coords.East + east,
				},
			}
			action.Execute()
		},
	},
	"/pickup": {
		Args:        []string{"itemid or name"},
		Description: "Pickup item of a given name or specific id",
		AllowInGame: true,
		Help: func(c *Client) {
		},
		Call: func(c *Client, args []string) {
			if c.Player.Loc == nil {
				return
			}

			if len(c.Player.Loc.Items) == 0 {
				c.SendEvent(&responses.Generic{
					Messages: []string{"There are no items laying on the ground."},
				})
				return
			}

			var puItem *Item

			if len(args) > 0 {
				nameArg := strings.ToLower(args[0])
				idArg := args[0]
				var itmById *Item
				var itmByName *Item

				for item := range c.Player.Loc.Items {
					if item.ID == idArg {
						itmById = item
						itmByName = nil
						break
					}

					if strings.HasPrefix(strings.ToLower(item.GetName()), nameArg) {
						itmByName = item
						break
					}
				}

				if itmById != nil {
					puItem = itmById
				}
				if itmByName != nil {
					puItem = itmByName
				}

				if puItem == nil {
					c.SendEvent(&responses.Generic{
						Messages: []string{"There are no items on the ground beginning with that."},
					})
					return
				}
			} else {
				for item := range c.Player.Loc.Items {
					puItem = item
					break
				}
			}

			logger.LogItems(c.Player.Name, "pickup", puItem.TemplateName, c.Player.Loc.Coords.North, c.Player.Loc.Coords.East, c.Player.Loc.Coords.City)

			c.Player.Loc.RemoveItem <- &ItemMoved{
				Item:   puItem,
				By:     c.Player.Name,
				Player: c.Player,
			}
		},
	},
	"/drop": {
		Args:        []string{"inventory slot"},
		Description: "Drop an item at a given inventory slot",
		AllowInGame: true,
		Help: func(c *Client) {
		},
		Call: func(c *Client, args []string) {
			if len(args) == 0 {
				return
			}

			if c.Player.Loc == nil {
				return
			}

			slotIndex, err := strconv.ParseUint(args[0], 10, 32)
			if err != nil {
				fmt.Println(err.Error())
				return
			}

			event := c.Player.Inventory.drop(int(slotIndex))
			if event != nil {
				c.Player.Loc.AddItem <- event
			}

			logger.LogItems(c.Player.Name, "drop", event.Item.TemplateName, c.Player.Loc.Coords.North, c.Player.Loc.Coords.East, c.Player.Loc.Coords.City)
		},
	},
	"/npcs": {
		Args:          []string{},
		Description:   "List of all NPCs.",
		AllowInGame:   true,
		AllowUnAuthed: true,
		AllowAuthed:   true,
		Help: func(c *Client) {
		},
		Call: func(c *Client, _ []string) {
			headings := []string{"Name", "health", "Accuracy", "Damage", "Armor (Range)", "Armor (Melee)"}
			data := [][]string{}

			for _, tmpl := range NpcTemplates {
				dmg := 2
				drRange := 0
				drMelee := 0

				if tmpl.Equipment != nil {
					if gunId, ok := tmpl.Equipment[ItemTypeGun]; ok {
						gun := ItemsList[gunId]

						if gun != nil {
							dmg = int(gun.Damage)
						}

						if ammoId, ok := tmpl.Equipment[ItemTypeAmmo]; ok {
							ammo := ItemsList[ammoId]

							if ammo != nil {
								dmg += int(ammo.Damage)
							}
						}
					}

					if meleeId, ok := tmpl.Equipment[ItemTypeMelee]; ok {
						weapon := ItemsList[meleeId]

						if weapon != nil {
							dmg = int(weapon.Damage)
						}
					}

					if armorId, ok := tmpl.Equipment[ItemTypeArmor]; ok {
						armor := ItemsList[armorId]

						if armor != nil {
							drMelee = int(armor.ArmorGuns)
							drRange = int(armor.ArmorMelee)
						}
					}
				}

				data = append(data, []string{
					tmpl.Title,
					fmt.Sprintf("%d", tmpl.Health),
					fmt.Sprintf("%.2f", tmpl.SkillAcc),
					fmt.Sprintf("%d", dmg),
					fmt.Sprintf("%d", drRange),
					fmt.Sprintf("%d", drMelee),
				})
			}

			c.SendEvent(&responses.Generic{
				Ascii:    true,
				Messages: internal.ToTable(headings, data),
			})
		},
	},
	"/help": {
		Args:          []string{},
		Description:   "List of all general commands and controls.",
		AllowInGame:   true,
		AllowUnAuthed: true,
		AllowAuthed:   true,
		Help: func(c *Client) {
		},
		Call: func(c *Client, _ []string) {
			c.SendEvent(&responses.Generic{
				Ascii:    true,
				Messages: []string{" ", " ", "Here is a list of all common commands:"},
			})

			headings := []string{"Command", "Description"}
			commands := [][]string{
				{"/<command> help", "Most commands have a help menu, explaining its use."},
				{"/here", "Will tell you what commands are available at a given location (if any)"},
				{"/say", "Send a chat message visible only to those in the same location."},
				{"/global", "Send a chat message visible to all online players."},
				{"/pm <name> <msg>", "Send a private chat message to the specified player."},
				{"/buy <name>", "Open drug dealer buy menu"},
				{"/sell <name>", "Open drug addict sell menu"},
				{"/shop", "Open the shop window (eg. arms dealer)"},
				{"/aim <name>", "Takes aim at a target, required before you attack"},
				{"/unaim", "Removes your lock on the target"},
				{"/flee <dir>", "Escapes from battle, but you loose items."},
				{"/autoattack", "Toggles auto-attack on/off."},
			}

			c.SendEvent(&responses.Generic{
				Ascii:    true,
				Messages: internal.ToTable(headings, commands),
			})

			c.SendEvent(&responses.Generic{
				Ascii:    true,
				Messages: []string{" ", " ", "This is all the controls:"},
			})

			ctrlHeadings := []string{"Keyboard Key", "Alt. Key", "Description"}
			ctrls := [][]string{
				{"W", "K, Arrow Up", "Move 1 space north"},
				{"A", "H, Arrow Left", "Move 1 space West"},
				{"S", "J, Arrow Down", "Move 1 space South"},
				{"D", "L, Arrow Right", "Move 1 space East"},
				{"R", "", "Clears event log, and gets the location latest changes"},
				{"I", "Enter", "Focuses the command input if not already focused."},
				{"Esc.", "", "Unfocuses the command input, if in focus"},
				{"1", "", "Strike your target with your fist (2dmg)"},
				{"2", "", "Strike your target with your melee weapon (weapon dmg)"},
				{"3", "", "Shoot your target with you gun (weapon dmg)"},
			}

			c.SendEvent(&responses.Generic{
				Ascii:    true,
				Messages: internal.ToTable(ctrlHeadings, ctrls),
			})

			c.SendEvent(&responses.Generic{
				Messages: []string{
					"Deal drug to gain money and increase your reputation. The more reputation you have, the more things you can buy and do.",
					"This game is full-loot PvPvE, anything player or NPC carry will be dropped when killed, and any cash on them will go to the killer.",
					"",
				},
			})
		},
	},
	"/here": {
		Args:        []string{},
		Description: "Gives you information about available commands at a given location",
		AllowInGame: true,
		Help: func(c *Client) {
		},
		Call: func(c *Client, _ []string) {
			if c.Player.Loc == nil {
				return
			}

			if len(c.Player.Loc.Buildings) == 0 {
				return
			}

			headings := []string{"Command", "Description", "Example"}
			commands := [][]string{}
			for _, b := range c.Player.Loc.Buildings {
				for cmd, c := range b.Commands {
					commands = append(commands, []string{
						cmd + " " + strings.Join(c.Args, " "),
						c.Description,
						c.Example,
					})
				}
			}

			c.SendEvent(&responses.Generic{
				Ascii:    true,
				Messages: internal.ToTable(headings, commands),
			})
		},
	},
	"/pm": {
		Args:        []string{"user", "message"},
		Description: "Send a private message to a player",
		AllowInGame: true,
		Help: func(c *Client) {
		},
		Call: func(c *Client, args []string) {
			if len(args) != 2 {
				return
			}

			playerName := args[0]
			message := strings.Join(args[1:], " ")
			var player *Entity
			for tc := range c.Game.Clients {
				if tc.Player == nil {
					return
				}

				if strings.ToLower(tc.Player.Name) == playerName {
					player = tc.Player
					break
				}
			}

			if player == nil {
				c.SendEvent(&responses.Generic{
					Messages: []string{"There are no one online going by that name."},
				})
				return
			}

			logger.LogChat("private", c.Player.Name, message, player.Name)

			msg := []byte(message)
			msg = bytes.TrimSpace(bytes.ReplaceAll(msg, []byte{'\n'}, []byte{' '}))

			player.Client.SendEvent(&responses.Chat{
				Type:   responses.ChatType_CHAT_TYPE_PRIVATE,
				Player: c.Player.PlayerGameFrame(),
				Msg:    string(msg),
			})
		},
	},
	"/say": {
		Args:        []string{"message"},
		Description: "Send a message locally",
		AllowInGame: true,
		Help: func(c *Client) {
		},
		Call: func(c *Client, args []string) {
			if len(args) == 0 {
				return
			}

			if c.Player.Loc == nil {
				return
			}

			message := strings.Join(args, " ")
			logger.LogChat("local", c.Player.Name, message, "")

			msg := []byte(message)
			msg = bytes.TrimSpace(bytes.ReplaceAll(msg, []byte{'\n'}, []byte{' '}))

			event := responses.Generic{
				Status: responses.ResponseStatus_RESPONSE_STATUS_INFO,
				Messages: []string{
					fmt.Sprintf("(Local) %s: %s", c.Player.Name, string(msg)),
				},
			}

			c.Player.Loc.Events <- &ClientResponse{
				Payload: &event,
			}
		},
	},
	"/global": {
		Args:          []string{"message"},
		Description:   "Send a message to the global chat.",
		AllowInGame:   true,
		AllowAuthed:   false,
		AllowUnAuthed: false,
		Help: func(c *Client) {
		},
		Call: func(c *Client, args []string) {
			if len(args) == 0 {
				return
			}

			message := strings.Join(args, " ")

			logger.LogChat("global", c.Player.Name, message, "")

			msg := []byte(message)
			msg = bytes.TrimSpace(bytes.ReplaceAll(msg, []byte{'\n'}, []byte{' '}))

			event := responses.Chat{
				Type:   responses.ChatType_CHAT_TYPE_GLOBAL,
				Player: c.Player.PlayerGameFrame(),
				Msg:    string(msg),
			}

			c.Game.GlobalEvents <- &event
		},
	},
	"/refresh": {
		Args:          []string{},
		Description:   "Refresh the game frame",
		AllowInGame:   true,
		AllowAuthed:   false,
		AllowUnAuthed: true,
		Help: func(c *Client) {
		},
		Call: func(c *Client, _ []string) {
			if c.Player == nil {
				c.Game.SendMOTD(c)
				return
			}

			c.Player.sendGameFrame(true)
		},
	},
	"/move": {
		Args:          []string{"direction"},
		Description:   "Move in a provided direction.",
		AllowInGame:   true,
		AllowAuthed:   false,
		AllowUnAuthed: false,
		Help: func(c *Client) {
		},
		Call: func(c *Client, args []string) {
			if len(args) == 0 {
				return
			}

			if c.Player.Loc == nil {
				return
			}

			if len(c.Player.TargetedBy) > 0 {
				names := []string{}
				for p := range c.Player.TargetedBy {
					names = append(names, p.Name)
				}

				c.SendEvent(&responses.Generic{
					Status: responses.ResponseStatus_RESPONSE_STATUS_ERROR,
					Messages: []string{
						fmt.Sprintf("You cannot move while being held up by: %s", strings.Join(names, ",")),
						"You can flee the area using: \"/flee <direction>\", eg \"/free north\", but you will drop some items.",
					},
				})
				return
			}

			now := time.Now().UnixMilli()
			if c.Player.LastMove+settings.PlayerMoveDelayMs > now {
				return
			}
			c.Player.LastMove = now

			north := c.Player.Loc.Coords.North
			east := c.Player.Loc.Coords.East

			switch strings.ToLower(args[0]) {
			case "up", "w", "north":
				north += 1
			case "down", "s", "south":
				north += -1
			case "left", "a", "west":
				east += -1
			case "right", "d", "east":
				east += 1
			}

			if north < 0 || north > int(c.Player.Loc.City.Height) || east < 0 || east > int(c.Player.Loc.City.Width) {
				c.SendEvent(&responses.Generic{
					Messages: []string{
						"You cannot go any further in that direction",
					},
				})
				return
			}

			newLocation := Coordinates{
				North: int(north),
				East:  int(east),
			}

			if val, ok := c.Player.Loc.City.Grid[newLocation.toString()]; ok {
				val.PlayerJoin <- c
			}
		},
	},
	"/new": {
		Args:          []string{"name", "city"},
		Description:   "Creates a new character",
		AllowInGame:   false,
		AllowAuthed:   true,
		AllowUnAuthed: false,
		Help: func(c *Client) {
		},
		Call: func(c *Client, args []string) {
			_, _, err := c.Game.GetUserCharacter(c.UserId)
			if err == nil {
				c.SendEvent(&responses.Generic{
					Status: responses.ResponseStatus_RESPONSE_STATUS_ERROR,
					Messages: []string{
						"You cannot crate a character. You are not logged in",
					},
				})
				return
			}

			if len(args) < 2 {
				c.SendEvent(&responses.Generic{
					Status: responses.ResponseStatus_RESPONSE_STATUS_ERROR,
					Messages: []string{
						"Invalid command. The format is:  \"/login username password\"",
					},
				})
				return
			}

			name := args[0]
			city := args[1]

			if _, ok := c.Game.World[city]; !ok {
				list := []string{}

				for c := range c.Game.World {
					list = append(list, c)
				}

				c.SendEvent(&responses.Generic{
					Status: responses.ResponseStatus_RESPONSE_STATUS_ERROR,
					Messages: []string{
						"Invalid City. Your options are:",
						strings.Join(list, ", "),
					},
				})
				return
			}

			row := c.Game.DbConn.QueryRow("SELECT id FROM characters WHERE LOWER(name) = ?", strings.ToLower(name))

			char := models.Character{}
			row.Scan(&char.Id)

			if char.Id != 0 {
				c.SendEvent(&responses.Generic{
					Status: responses.ResponseStatus_RESPONSE_STATUS_ERROR,
					Messages: []string{
						"That name is already taken.",
					},
				})
				return
			}

			hometown := c.Game.World[city]
			startLocation := hometown.RandomLocation()

			newChar := models.Character{
				UserId:        c.UserId,
				Name:          name,
				Cash:          settings.PlayerStartCash,
				Reputation:    settings.PlayerStartReputation,
				Bank:          settings.PlayerStartBank,
				Health:        settings.PlayerMaxHealth,
				SkillAcc:      settings.PlayerStartSkillAcc,
				SkillTrack:    settings.PlayerStartSkillTrack,
				SkillHide:     settings.PlayerStartSkillHide,
				SkillSnoop:    settings.PlayerStartSkillSnoop,
				SkillSearch:   settings.PlayerStartSkillSearch,
				Hometown:      hometown.ShortName,
				LocationNorth: startLocation.North,
				LocationEast:  startLocation.East,
				LocationCity:  hometown.ShortName,
				CreatedAt:     time.Now().Unix(),
			}

			result, err := c.Game.DbConn.Exec(
				"INSERT INTO characters (user_id, name, reputation, health, npc_kill, player_kills, cash, bank, hometown, skill_acc, skill_track, skill_hide, skill_snoop, skill_search, location_n, location_e, location_city, created_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
				&newChar.UserId,
				&newChar.Name,
				&newChar.Reputation,
				&newChar.Health,
				0,
				0,
				&newChar.Cash,
				&newChar.Bank,
				&newChar.Hometown,
				&newChar.SkillAcc,
				&newChar.SkillTrack,
				&newChar.SkillHide,
				&newChar.SkillSnoop,
				&newChar.SkillSearch,
				&newChar.LocationNorth,
				&newChar.LocationEast,
				&newChar.LocationCity,
				&newChar.CreatedAt,
			)
			if err != nil {
				c.SendEvent(&responses.Generic{
					Status: responses.ResponseStatus_RESPONSE_STATUS_ERROR,
					Messages: []string{
						"Failed to create character, system error.",
					},
				})
				return
			}

			lastId, err := result.LastInsertId()
			if err != nil {
				log.Print(err)
				c.SendEvent(&responses.Generic{
					Status: responses.ResponseStatus_RESPONSE_STATUS_ERROR,
					Messages: []string{
						"Failed to create character, system error.",
					},
				})
				return
			}

			newChar.Id = uint64(lastId)
			player, lastLocation, err := c.Game.GetUserCharacter(newChar.UserId)
			if err != nil {
				log.Print(err)
				c.SendEvent(&responses.Generic{
					Status: responses.ResponseStatus_RESPONSE_STATUS_ERROR,
					Messages: []string{
						"Failed load in your character, system error.",
					},
				})
				return
			}

			c.Game.LoginPlayer(c, player, lastLocation)
		},
	},
	"/authenticate": {
		Args:          []string{"username", "password"},
		Description:   "Login to your account.",
		AllowInGame:   false,
		AllowAuthed:   false,
		AllowUnAuthed: true,
		Help: func(c *Client) {
		},
		Call: func(c *Client, args []string) {
			if len(args) < 2 {
				c.SendEvent(&responses.Generic{
					Status: responses.ResponseStatus_RESPONSE_STATUS_ERROR,
					Messages: []string{
						"Invalid command. The format is:  \"/login username password\"",
					},
				})
				return
			}
			email := args[0]
			password := args[1]

			row := c.Game.DbConn.QueryRow("SELECT id, password, user_type FROM users WHERE email = ? LIMIT 1", email)
			if row == nil {
				c.SendEvent(&responses.Generic{
					Status: responses.ResponseStatus_RESPONSE_STATUS_ERROR,
					Messages: []string{
						"Invalid username and password combination.",
					},
				})
				return
			}

			user := models.User{}
			err := row.Scan(&user.Id, &user.Password, &user.UserType)
			if user.Id == 0 || err != nil {
				c.SendEvent(&responses.Generic{
					Status: responses.ResponseStatus_RESPONSE_STATUS_ERROR,
					Messages: []string{
						"Invalid username and password combination.",
					},
				})
				return
			}

			if ok, err := internal.CheckPassword(password, user.Password); !ok || err != nil {
				c.SendEvent(&responses.Generic{
					Status: responses.ResponseStatus_RESPONSE_STATUS_ERROR,
					Messages: []string{
						"Invalid username and password combination.",
					},
				})
				return
			}

			c.Authenticated = true
			c.UserId = user.Id
			c.UserType = uint8(user.UserType)

			player, lastLocation, err := c.Game.GetUserCharacter(user.Id)
			if err != nil {
				c.SendEvent(&responses.Generic{
					Status: responses.ResponseStatus_RESPONSE_STATUS_SUCCESS,
					Messages: []string{
						"Your have been logged in, however you do not have a character yet.",
						"To create a new character type /new",
					},
				})
				return
			}

			c.Game.LoginPlayer(c, player, lastLocation)
		},
	},
	"/demand": {
		Args:         []string{},
		Description:  "update drug demand for the city",
		AllowInGame:  true,
		AdminCommand: true,
		Help: func(c *Client) {
		},
		Call: func(c *Client, _ []string) {
			c.Player.Loc.City.UpdateDrugDemand()
		},
	},
	"/save": {
		Args:         []string{},
		Description:  "Save all online players",
		AllowInGame:  true,
		AdminCommand: true,
		Help: func(c *Client) {
		},
		Call: func(c *Client, _ []string) {
			c.Game.Save()
		},
	},
	"/restock": {
		Args:         []string{},
		Description:  "Restocks all drugs on the server",
		AllowInGame:  true,
		AdminCommand: true,
		Help: func(c *Client) {
		},
		Call: func(c *Client, _ []string) {
			c.Game.Restock()
		},
	},
	"/additem": {
		Args:         []string{"itemid"},
		Description:  "Spawn a new item",
		AllowInGame:  true,
		AdminCommand: true,
		Help: func(c *Client) {
		},
		Call: func(c *Client, args []string) {
			if len(args) == 0 {
				return
			}

			if c.Player.Loc == nil {
				return
			}

			item, ok := NewItem(args[0])
			if !ok {
				return
			}

			c.Player.Loc.AddItem <- &ItemMoved{
				Item: item,
				By:   c.Player.Name,
			}
		},
	},
}
View Source
var FemaleNames = []string{}/* 300 elements not displayed */
View Source
var ItemTemplates = map[string]ItemTemplate{

	"crack": {
		Name:        "Crack",
		Description: "1 Gram",
		ItemType:    ItemTypeDrug,
		BasePrice:   30,
		MaxPrice:    60,
		UseEffect:   "usedrug",
	},
	"coke": {
		Name:        "Cocaine",
		Description: "1 Gram",
		ItemType:    ItemTypeDrug,
		BasePrice:   60,
		MaxPrice:    100,
		UseEffect:   "usedrug",
	},
	"heroine": {
		Name:        "Heroin",
		Description: "1 Gram",
		ItemType:    ItemTypeDrug,
		BasePrice:   150,
		MaxPrice:    500,
		UseEffect:   "usedrug",
	},
	"meth": {
		Name:        "Meth",
		Description: "1 Gram",
		ItemType:    ItemTypeDrug,
		BasePrice:   20,
		MaxPrice:    40,
		UseEffect:   "usedrug",
	},
	"weed": {
		Name:        "Weed",
		Description: "7 Grams / a quater ounce",
		ItemType:    ItemTypeDrug,
		BasePrice:   25,
		MaxPrice:    50,
		UseEffect:   "usedrug",
	},
	"fentanyl": {
		Name:        "Fentanyl",
		Description: "1 Pill",
		ItemType:    ItemTypeDrug,
		BasePrice:   25,
		MaxPrice:    50,
		UseEffect:   "usedrug",
	},
	"pcp": {
		Name:        "PCP",
		Description: "1 tablet",
		ItemType:    ItemTypeDrug,
		BasePrice:   5,
		MaxPrice:    15,
		UseEffect:   "usedrug",
	},
	"ketamine": {
		Name:        "Ketamine",
		Description: "A dose",
		ItemType:    ItemTypeDrug,
		BasePrice:   20,
		MaxPrice:    30,
		UseEffect:   "usedrug",
	},

	"iia_armor": {
		Name:        "Level IIA Body Armor",
		Description: "Designed to protect against 9mm.",
		ItemType:    ItemTypeArmor,
		BasePrice:   300,
		ArmorGuns:   3,
		MinRep:      RanksList[6].MinRep,
	},
	"ii_armor": {
		Name:        "Level II Body Armor",
		Description: "Provides protection against upto .357 Magnum.",
		ItemType:    ItemTypeArmor,
		BasePrice:   700,
		ArmorGuns:   7,
		MinRep:      RanksList[10].MinRep,
	},
	"iiia_armor": {
		Name:        "Level IIIA Body Armor",
		Description: "Designed to protect against handgun threats, including .44 Magnum.",
		ItemType:    ItemTypeArmor,
		BasePrice:   1100,
		ArmorGuns:   11,
		MinRep:      RanksList[14].MinRep,
	},
	"iii_armor": {
		Name:        "Level III Body Armor",
		Description: "Designed to protect against rifle threats, including 7.62x51mm (M80) ball.",
		ItemType:    ItemTypeArmor,
		BasePrice:   1500,
		ArmorGuns:   15,
		MinRep:      RanksList[18].MinRep,
	},
	"iv_armor": {
		Name:        "Level IV Body Armor",
		Description: "Provides protection against armor-piercing rifle rounds, such as .30-06 M2 AP.",
		ItemType:    ItemTypeArmor,
		BasePrice:   1900,
		ArmorGuns:   19,
		MinRep:      RanksList[22].MinRep,
	},

	"beretta92": {
		Name:        "Beretta 92",
		Description: "A widely used semi-automatic pistol in 9mm, known for its accuracy and reliability.",
		ItemType:    ItemTypeGun,
		BasePrice:   300,
		Damage:      3,
		MinRep:      RanksList[5].MinRep,
	},
	"glock22": {
		Name:        "Glock 22",
		Description: "A popular law enforcement pistol chambered in .40 S&W, favored for its versatility.",
		ItemType:    ItemTypeGun,
		BasePrice:   500,
		Damage:      5,
		MinRep:      RanksList[7].MinRep,
	},
	"sigp320": {
		Name:        "Sig Sauer P220",
		Description: "The P220 is known for its reliability and is a favorite among enthusiasts. Chambered in 10mm",
		ItemType:    ItemTypeGun,
		BasePrice:   700,
		Damage:      7,
		MinRep:      RanksList[9].MinRep,
	},
	"sw610": {
		Name:        "Smith & Wesson Model 610",
		Description: "A versatile and durable stainless steel semi-automatic revolver chambered for .357 Magnum.",
		ItemType:    ItemTypeGun,
		BasePrice:   900,
		Damage:      9,
		MinRep:      RanksList[11].MinRep,
	},
	"1911": {
		Name:        "Colt 1911",
		Description: "One of the most iconic handguns in the world. Chambered in .45 ACP",
		ItemType:    ItemTypeGun,
		BasePrice:   1100,
		Damage:      11,
		MinRep:      RanksList[13].MinRep,
	},
	"ragingbull": {
		Name:        "Taurus Raging Bull",
		Description: "A large-framed revolver recognized for its power and ruggedness. Chambered in .44 Magnum.",
		ItemType:    ItemTypeGun,
		BasePrice:   1300,
		Damage:      13,
		MinRep:      RanksList[15].MinRep,
	},
	"ar-15": {
		Name:        "AR-15 Rifle",
		Description: "The AR-15 is a widely used semi-automatic rifle known for its modularity and adaptability. Chambered in 5.56x45mm NATO",
		ItemType:    ItemTypeGun,
		BasePrice:   1500,
		Damage:      15,
		MinRep:      RanksList[17].MinRep,
	},
	"ak47": {
		Name:        "AK-47",
		Description: "The AK-47 is a legendary and rugged assault rifle known for its reliability. Chambered in 7.62x39mm",
		ItemType:    ItemTypeGun,
		BasePrice:   1700,
		Damage:      17,
		MinRep:      RanksList[19].MinRep,
	},
	"scarh": {
		Name:        "FN SCAR-H",
		Description: "A versatile battle rifle used by military forces around the world. Chambered in 7.62x51mm NATO",
		ItemType:    ItemTypeGun,
		BasePrice:   1900,
		Damage:      19,
		MinRep:      RanksList[21].MinRep,
	},
	"m82": {
		Name:        "Barrett M82",
		Description: "The Barrett M82 is a renowned anti-materiel rifle chambered in .50 BMG.",
		ItemType:    ItemTypeGun,
		BasePrice:   2100,
		Damage:      21,
		MinRep:      RanksList[23].MinRep,
	},

	"subsonic": {
		Name:        "Subsonic Ammo",
		Description: "Subsonic rounds are typically lower in power due to their reduced velocity, making them quieter.",
		ItemType:    ItemTypeAmmo,
		BasePrice:   100,
		Damage:      1,
		AmmoWear:    0.0025,
		MinRep:      RanksList[5].MinRep,
		Amount:      15,
	},
	"sdammo": {
		Name:        "Standard Ammo",
		Description: "Standard ammunition, often referred to as \"ball\" ammunition, is the baseline cartridge for a particular caliber.",
		ItemType:    ItemTypeAmmo,
		BasePrice:   300,
		Damage:      3,
		AmmoWear:    0.0040,
		MinRep:      RanksList[9].MinRep,
		Amount:      15,
	},
	"plusp": {
		Name:        "+P Ammo",
		Description: "Cartridges labeled as +P are loaded with higher powder charges than standard loads of the same caliber.",
		ItemType:    ItemTypeAmmo,
		BasePrice:   500,
		Damage:      5,
		AmmoWear:    0.0055,
		MinRep:      RanksList[13].MinRep,
		Amount:      15,
	},
	"pluspplus": {
		Name:        "+P+ Ammo",
		Description: "These cartridges are loaded with significantly more powder, further increasing muzzle velocity and energy.",
		ItemType:    ItemTypeAmmo,
		BasePrice:   700,
		Damage:      7,
		AmmoWear:    0.0070,
		MinRep:      RanksList[17].MinRep,
		Amount:      15,
	},
	"apammo": {
		Name:        "AP Ammo",
		Description: "These cartridges are loaded with significantly more powder, further increasing muzzle velocity and energy.",
		ItemType:    ItemTypeAmmo,
		BasePrice:   900,
		Damage:      9,
		AmmoWear:    0.0085,
		MinRep:      RanksList[21].MinRep,
		Amount:      15,
	},

	"brassknuckle": {
		Name:        "Brass Knuckle",
		Description: "Brass knuckles, a classic street weapon, provide a discreet and formidable edge in close combat.",
		ItemType:    ItemTypeMelee,
		BasePrice:   300,
		Damage:      3,
		MinRep:      RanksList[6].MinRep,
	},
	"pipewrench": {
		Name:        "Pipe Wrench",
		Description: "A heavy-duty pipe wrench, perfect for tight spaces and DIY repairs. A symbol of blue-collar craftsmanship.",
		ItemType:    ItemTypeMelee,
		BasePrice:   500,
		Damage:      5,
		MinRep:      RanksList[8].MinRep,
	},
	"crowbar": {
		Name:        "Crowbar",
		Description: "A versatile tool and makeshift weapon, the crowbar can pry open doors, crates, and skulls with equal efficiency.",
		ItemType:    ItemTypeMelee,
		BasePrice:   700,
		Damage:      7,
		MinRep:      RanksList[10].MinRep,
	},
	"switchblade": {
		Name:        "Switchblade",
		Description: "The switchblade, a compact folding knife, offers quick and discreet access for self-defense or utility in a pinch.",
		ItemType:    ItemTypeMelee,
		BasePrice:   900,
		Damage:      9,
		MinRep:      RanksList[12].MinRep,
	},
	"bbbat": {
		Name:        "Baseball Bat",
		Description: "A solid baseball bat, ideal for both sports and as an improvised weapon. Swing for the fences or fend off threats.",
		ItemType:    ItemTypeMelee,
		BasePrice:   1100,
		Damage:      11,
		MinRep:      RanksList[14].MinRep,
	},
	"fireaxe": {
		Name:        "Fire Axe",
		Description: "A heavy-duty fire axe, designed for breaking through obstacles and providing a powerful, life-saving tool in emergencies.",
		ItemType:    ItemTypeMelee,
		BasePrice:   1300,
		Damage:      13,
		MinRep:      RanksList[16].MinRep,
	},
	"machete": {
		Name:        "Machete",
		Description: "The machete, a rugged cutting tool, is essential for clearing foliage or defending against the wild and unexpected.",
		ItemType:    ItemTypeMelee,
		BasePrice:   1500,
		Damage:      15,
		MinRep:      RanksList[18].MinRep,
	},
	"katana": {
		Name:        "Katana",
		Description: "The katana, a legendary Japanese sword, balances precision and power, making it a deadly choice for any skilled warrior.",
		ItemType:    ItemTypeMelee,
		BasePrice:   1700,
		Damage:      17,
		MinRep:      RanksList[20].MinRep,
	},
	"chainsaw": {
		Name:        "Chainsaw",
		Description: "A fearsome chainsaw, designed for heavy-duty cutting and capable of unleashing raw, mechanical power in the right hands.",
		ItemType:    ItemTypeMelee,
		BasePrice:   1900,
		Damage:      19,
		MinRep:      RanksList[22].MinRep,
	},

	"stabvest": {
		Name:        "Stab-Resistant Vest",
		Description: "Made of materials such as Kevlar and laminated fabrics. Designed to resist punctures from knives and other sharp-edged weapons.",
		ItemType:    ItemTypeArmor,
		BasePrice:   400,
		ArmorMelee:  4,
		MinRep:      RanksList[10].MinRep,
	},
	"chainmail": {
		Name:        "Chainmail",
		Description: "Modern chainmail is made from materials like steel or titanium rings. It provides good protection against slashing attacks from knives and swords.",
		ItemType:    ItemTypeArmor,
		BasePrice:   700,
		ArmorMelee:  7,
		MinRep:      RanksList[14].MinRep,
	},
	"hardarmor": {
		Name:        "Hard Plate Armor",
		Description: "Usually made from ceramics, steel, or composite materials. They provide excellent protection against knife and sword strikes",
		ItemType:    ItemTypeArmor,
		BasePrice:   1000,
		ArmorMelee:  10,
		MinRep:      RanksList[18].MinRep,
	},

	"smartphone": {
		Name:        "Smart Phone",
		Description: fmt.Sprintf("Use the get the location of druggies and dealer, for the small fee of $%d to your informant of cause.", settings.SmartPhoneCost),
		ItemType:    ItemTypeSmartPhone,
		BasePrice:   350,
		UseEffect:   "smartphone",
	},

	"deserteagle": {
		Name:        "Desert Eagle",
		Description: "The Desert Eagle .50 AE is a semi-automatic handgun that stands out for its considerable size and firepower. Chambered in .50 AE",
		ItemType:    ItemTypeGun,
		BasePrice:   500,
		Damage:      15,
	},
	"brokenbottle": {
		Name:        "Broken Bottle",
		Description: "A broken bottle that has been damaged, resulting in sharp and jagged edges.",
		ItemType:    ItemTypeMelee,
		BasePrice:   50,
		Damage:      2,
	},
	"guitar": {
		Name:        "Guitar",
		Description: "A six-stringed musical instrument known for its versatility and soulful melodies.",
		ItemType:    ItemTypeMelee,
		BasePrice:   100,
		Damage:      4,
	},
	"leadpipe": {
		Name:        "Lead Pipe",
		Description: "A heavy and sturdy cylindrical tube typically made of lead or other dense materials.",
		ItemType:    ItemTypeMelee,
		BasePrice:   100,
		Damage:      4,
	},
	"parcel": {
		Name:        "Parcel",
		Description: "A securely wrapped package or envelope intended for delivery or transport.",
		ItemType:    ItemTypeMelee,
		BasePrice:   50,
		Damage:      2,
	},
	"mac-10": {
		Name:        "MAC-10",
		Description: "\"Military Armament Corporation Model 10\" is a compact, blowback-operated submachine gun. Chambered in .45 ACP",
		ItemType:    ItemTypeGun,
		BasePrice:   400,
		Damage:      11,
	},
	"bikelock": {
		Name:        "Bike Lock",
		Description: "In the hands of a resourceful individual, it becomes an improvised weapon, offering reach and a blunt force impact",
		ItemType:    ItemTypeMelee,
		BasePrice:   75,
		Damage:      3,
	},

	"goldchain": {
		Name:        "Gold Chain",
		Description: "A accessory made of linked gold segments.",
		ItemType:    ItemTypeTrash,
		BasePrice:   300,
	},
	"festivalticket": {
		Name:        "Festival Ticket",
		Description: "A ticket for music festival",
		ItemType:    ItemTypeTrash,
		BasePrice:   150,
	},
	"sunglasses": {
		Name:        "Sun Glasses",
		Description: "Old, but decent brand.",
		ItemType:    ItemTypeTrash,
		BasePrice:   100,
	},
	"deliverypackage": {
		Name:        "\"Amazone\" Package",
		Description: "Something is inside.",
		ItemType:    ItemTypeTrash,
		BasePrice:   100,
	},
	"currentthing": {
		Name:        "Current Thing",
		Description: "An item which shows you support the \"Current Thing\".",
		ItemType:    ItemTypeTrash,
		BasePrice:   100,
	},
	"policebadge": {
		Name:        "Police Badge",
		Description: "Standard police badge.",
		ItemType:    ItemTypeTrash,
		BasePrice:   300,
	},
}
View Source
var MaleNames = []string{}/* 300 elements not displayed */
View Source
var NpcTemplates = map[NPCType]NpcTemplate{
	DrugDealer: {
		NpcType:  DrugDealer,
		Title:    "Drug Dealer",
		Health:   500,
		Cash:     1000,
		Rep:      -1750,
		SkillAcc: 75.0,
		Gender:   GenderMale,
		Equipment: map[IType]string{
			ItemTypeArmor: "iiia_armor",
			ItemTypeGun:   "deserteagle",
			ItemTypeAmmo:  "plusp",
		},
	},
	DrugAddict: {
		NpcType:  DrugAddict,
		Title:    "Drug Addict",
		Gender:   GenderRandom,
		Health:   300,
		Cash:     500,
		Rep:      -1750,
		SkillAcc: 75.0,
		Equipment: map[IType]string{
			ItemTypeArmor: "iiia_armor",
			ItemTypeGun:   "ragingbull",
			ItemTypeAmmo:  "plusp",
		},
	},
	Homeless: {
		NpcType:  Homeless,
		Title:    "Homeless",
		Gender:   GenderRandom,
		Health:   80,
		Cash:     12,
		Rep:      25,
		SkillAcc: 15.0,
		Inventory: []string{
			"crack",
			"ketamine",
		},
		Equipment: map[IType]string{
			ItemTypeMelee: "brokenbottle",
		},
	},
	Tweaker: {
		NpcType:  Tweaker,
		Title:    "Tweaker",
		Gender:   GenderRandom,
		Health:   100,
		Cash:     25,
		Rep:      140,
		SkillAcc: 35.0,
		Inventory: []string{
			"meth",
			"meth",
		},
		Equipment: map[IType]string{
			ItemTypeMelee: "crowbar",
		},
	},
	Bouncer: {
		NpcType:  Bouncer,
		Title:    "Bouncer",
		Gender:   GenderRandom,
		Health:   110,
		Cash:     200,
		SkillAcc: 45.0,
		Rep:      272,
		Inventory: []string{
			"coke",
			"goldchain",
		},
		Equipment: map[IType]string{
			ItemTypeMelee: "bbat",
			ItemTypeArmor: "stabvest",
		},
	},
	Busker: {
		NpcType:  Busker,
		Title:    "Busker",
		Gender:   GenderRandom,
		Health:   100,
		Cash:     70,
		SkillAcc: 35,
		Rep:      70,
		Inventory: []string{
			"festivalticket",
			"weed",
		},
		Equipment: map[IType]string{
			ItemTypeMelee: "guitar",
		},
	},
	StreetVendor: {
		NpcType:  StreetVendor,
		Title:    "Street Vendor",
		Gender:   GenderRandom,
		Health:   140,
		Cash:     80,
		SkillAcc: 40,
		Rep:      70,
		Inventory: []string{
			"sunglasses",
		},
		Equipment: map[IType]string{
			ItemTypeMelee: "leadpipe",
		},
	},
	DeliveryDriver: {
		NpcType:  DeliveryDriver,
		Title:    "Delivery Driver",
		Gender:   GenderRandom,
		Health:   100,
		Cash:     10,
		SkillAcc: 40,
		Rep:      80,
		Inventory: []string{
			"deliverypackage",
			"smartphone",
		},
		Equipment: map[IType]string{
			ItemTypeMelee: "parcel",
		},
	},
	Tourist: {
		NpcType:  Tourist,
		Title:    "Tourist",
		Gender:   GenderRandom,
		Health:   65,
		Cash:     10,
		SkillAcc: 25,
		Rep:      33,
		Inventory: []string{
			"smartphone",
			"sunglasses",
		},
		Equipment: map[IType]string{},
	},
	StreetGangMember: {
		NpcType:  StreetGangMember,
		Title:    "Street Gang Member",
		Gender:   GenderRandom,
		Health:   150,
		Cash:     400,
		SkillAcc: 50,
		Rep:      412,
		Inventory: []string{
			"sdammo",
			"sdammo",
			"sdammo",
			"coke",
			"coke",
			"weed",
		},
		Equipment: map[IType]string{
			ItemTypeGun:   "mac-10",
			ItemTypeAmmo:  "sdammo",
			ItemTypeArmor: "ii_armor",
		},
	},
	Activist: {
		NpcType:  Activist,
		Title:    "Activist",
		Gender:   GenderRandom,
		Health:   40,
		Cash:     20,
		SkillAcc: 10,
		Rep:      20,
		Inventory: []string{
			"currentthing",
			"weed",
		},
		Equipment: map[IType]string{
			ItemTypeMelee: "bikelock",
		},
	},
	BeatCop: {
		NpcType:  BeatCop,
		Title:    "Beat Cop",
		Gender:   GenderRandom,
		Health:   100,
		Cash:     30,
		SkillAcc: 50,
		Rep:      200,
		Inventory: []string{
			"policebadge",
			"sdammo",
			"sdammo",
		},
		Equipment: map[IType]string{
			ItemTypeGun:   "glock22",
			ItemTypeAmmo:  "sdammo",
			ItemTypeArmor: "iia_armor",
		},
	},
	PoliceOfficer: {
		NpcType:  PoliceOfficer,
		Title:    "Police Officer",
		Gender:   GenderRandom,
		Health:   130,
		Cash:     30,
		SkillAcc: 60,
		Rep:      546,
		Inventory: []string{
			"policebadge",
			"sdammo",
			"sdammo",
		},
		Equipment: map[IType]string{
			ItemTypeGun:   "1911",
			ItemTypeAmmo:  "sdammo",
			ItemTypeArmor: "ii_armor",
		},
	},
}
View Source
var RanksList = [...]*Rank{
	{Name: "Crackhead", MinRep: -1400},
	{Name: "Street Trash", MinRep: -400},
	{Name: "Disrespectable Punk", MinRep: -100},
	{Name: "Nobody", MinRep: 0},
	{Name: "Wannabe", MinRep: 100},
	{Name: "Slacker", MinRep: 400},
	{Name: "Street Punk", MinRep: 1400},
	{Name: "Thug Wannabe", MinRep: 3400},
	{Name: "Thug", MinRep: 7000},
	{Name: "Hustler", MinRep: 12500},
	{Name: "Wanskta", MinRep: 20500},
	{Name: "Gangster", MinRep: 31700},
	{Name: "Soldier", MinRep: 46600},
	{Name: "Playa", MinRep: 65900},
	{Name: "Pimp", MinRep: 90500},
	{Name: "Pusher", MinRep: 120900},
	{Name: "Smuggler", MinRep: 158100},
	{Name: "Gun Runner", MinRep: 203000},
	{Name: "Mobster", MinRep: 256500},
	{Name: "Drug Lord", MinRep: 319400},
	{Name: "Capo", MinRep: 393000},
	{Name: "Underboss", MinRep: 478200},
	{Name: "Don", MinRep: 576100},
	{Name: "Kingpin", MinRep: 688000},
}
View Source
var UseEffectsList = map[string]*ItemUseEffect{
	"smartphone": {
		Use: func(c *Client, _ *Item, _ int) {
			if c.Player.Loc == nil {
				return
			}

			c.Player.Mu.Lock()
			defer c.Player.Mu.Unlock()

			var useCost int64 = settings.SmartPhoneCost

			if c.Player.Bank < useCost {
				c.SendEvent(&responses.Generic{
					Messages: []string{fmt.Sprintf("This information does not come for free, you don't have the $%d it costs in your bank.", useCost)},
				})
			}

			c.Player.Bank -= useCost

			ctrlHeadings := []string{"Who", "Last Known Location"}
			locations := [][]string{}

			for npc := range c.Player.Loc.City.NPCs[DrugDealer] {
				if npc.Loc == nil {
					continue
				}

				locations = append(locations, []string{
					fmt.Sprintf("%s the %s", npc.Name, npc.NpcTitle),
					fmt.Sprintf("N%d-E%d", npc.Loc.Coords.North, npc.Loc.Coords.East),
				})
			}

			for npc := range c.Player.Loc.City.NPCs[DrugAddict] {
				if npc.Loc == nil {
					continue
				}

				locations = append(locations, []string{
					fmt.Sprintf("%s the %s", npc.Name, npc.NpcTitle),
					fmt.Sprintf("N%d-E%d", npc.Loc.Coords.North, npc.Loc.Coords.East),
				})
			}

			c.SendEvent(&responses.Generic{
				Status:   responses.ResponseStatus_RESPONSE_STATUS_INFO,
				Messages: []string{fmt.Sprintf("Informant: \"(Phone) Your $%d was received. Alright, here is what I know..\"", useCost)},
			})

			c.SendEvent(&responses.Generic{
				Ascii:    true,
				Messages: internal.ToTable(ctrlHeadings, locations),
			})

			c.Player.PlayerSendStatsUpdate()
		},
	},
	"usedrug": {
		Use: func(c *Client, item *Item, slotIndex int) {
			c.Player.Mu.Lock()
			c.Player.Inventory.Mu.Lock()

			healthCost := settings.DrugUseHealthCost
			var repGain int64 = settings.DrugUseRepGain

			if c.Player.Health <= healthCost {
				c.SendEvent(&responses.Generic{
					Messages: []string{"Using this would kill you.."},
				})

				c.Player.Mu.Unlock()
				c.Player.Inventory.Mu.Unlock()
				return
			}

			c.Player.Health -= healthCost
			c.Player.Reputation += repGain
			c.Player.Mu.Unlock()

			item.Amount -= 1
			c.Player.Inventory.Mu.Unlock()

			if item.Amount <= 0 {
				c.Player.Inventory.drop(slotIndex)
			}

			c.SendEvent(&responses.Generic{
				Messages: []string{fmt.Sprintf("You use the %s. It didn't do your health any favours. (-%d Health, +%d Rep)", item.GetName(), healthCost, repGain)},
			})

			c.Player.PlayerSendInventoryUpdate()
			c.Player.PlayerSendStatsUpdate()
		},
	},
}

Functions

func CharacterToPlayer

func CharacterToPlayer(c *models.Character) (*Entity, *Coordinates, error)

func ExecuteCommand

func ExecuteCommand(c *Client, msg string)

func GenerateCities

func GenerateCities() map[string]*City

func GenerateItemsList

func GenerateItemsList()

func HandleWsClient

func HandleWsClient(g *Game, w http.ResponseWriter, r *http.Request)

func LinkRanks

func LinkRanks()

Types

type ActionType

type ActionType uint8

type Building

type Building struct {
	ID           string
	Name         string
	Description  string
	BuildingType BuildingType
	MerchantType responses.MerchantType
	Commands     map[string]*Command
	ShopStock    []*StockItem
	ShopBuyType  map[IType]int32
	Mu           sync.Mutex
}

func NewBuilding

func NewBuilding(t BuildingType) Building

func (*Building) OpenShop

func (e *Building) OpenShop(c *Client)

func (*Building) SyncShopInventory

func (e *Building) SyncShopInventory(c *Client)

type BuildingGameFrame

type BuildingGameFrame struct {
	Name        string   `json:"name"`
	Description string   `json:"description,omitempty"`
	Commands    []string `json:"commands"`
}

type BuildingLocation

type BuildingLocation struct {
	Coords    Coordinates
	Buildings []BuildingType
}

type BuildingTemplate

type BuildingTemplate struct {
	Name         string
	Commands     []string
	MerchantType responses.MerchantType
	ShopStock    map[string]int32
	ShopBuyType  map[IType]int32
}

type BuildingType

type BuildingType = uint8
const (
	BuildingTypeAirport  BuildingType = 1
	BuildingTypeHospital BuildingType = 2
	BuildingTypeBank     BuildingType = 3
	BuildingTypeBar      BuildingType = 4
	BuildingTypeArms     BuildingType = 5
	BuildingTypePawnShop BuildingType = 6
)

type CheckNameBody

type CheckNameBody struct {
	Name string
}

type CheckNameResponse

type CheckNameResponse struct {
	Taken bool `json:"available"`
	Valid bool `json:"valid"`
}

type City

type City struct {
	Name              string
	ShortName         string
	ISO               string
	Grid              map[string]*Location
	Width             uint
	Height            uint
	TravelCostMin     int64
	TravelCostMax     int64
	TravelCost        int64
	Players           map[*Client]bool
	PlayerJoin        chan *Client
	PlayerLeave       chan *Client
	CityEvents        chan bool
	Game              *Game
	NPCs              map[NPCType]map[*Entity]bool
	NpcSpawnList      map[NPCType]uint8
	BuildingLocations []BuildingLocation
	POILocations      []Coordinates
	DrugDemands       map[string]float32
	Mu                sync.Mutex
}

func NewCity

func NewCity(templ *CityTemplate) *City

k = city/urban size in km2 size = round(sqrt(k)) * 2

func (*City) GeneratePOIs

func (c *City) GeneratePOIs()

func (*City) RandomLocation

func (c *City) RandomLocation() Coordinates

func (*City) RandomiseTravelCost

func (c *City) RandomiseTravelCost()

func (*City) Setup

func (c *City) Setup()

func (*City) StartCityTimers

func (c *City) StartCityTimers()

func (*City) UpdateDrugDemand

func (c *City) UpdateDrugDemand()

type CityTemplate

type CityTemplate struct {
	Name              string
	ShortName         string
	ISO               string
	Width             uint8
	Height            uint8
	TravelCostMin     int64
	TravelCostMax     int64
	NpcSpawnList      map[NPCType]uint8
	BuildingLocations []BuildingLocation
}

type Client

type Client struct {
	Game          *Game
	Player        *Entity
	Authenticated bool
	UserId        uint64
	UUID          string
	UserType      uint8
	CombatLogging bool
	Connection    *websocket.Conn
	Send          chan protoreflect.ProtoMessage
	Mu            sync.Mutex
}

func (*Client) SendEvent

func (c *Client) SendEvent(msg protoreflect.ProtoMessage)

type ClientResponse

type ClientResponse struct {
	Payload protoreflect.ProtoMessage
	Ignore  map[uint64]bool
}

type CombatAction

type CombatAction struct {
	Attacker  *Entity
	Target    *Entity
	Action    ActionType
	Direction *Coordinates
	Success   bool
}

func (*CombatAction) Execute

func (c *CombatAction) Execute()

func (*CombatAction) SameLocation

func (c *CombatAction) SameLocation() bool

type Command

type Command struct {
	CommandKey    string
	Args          []string
	Description   string
	Example       string
	AllowInGame   bool
	AllowAuthed   bool
	AllowUnAuthed bool
	AdminCommand  bool
	Call          func(c *Client, args []string)
	Help          func(c *Client)
}

type Coordinates

type Coordinates struct {
	North   int    `json:"north"`
	East    int    `json:"east"`
	City    string `json:"city,omitempty"`
	POI     string `json:"poi,omitempty"`
	POIType uint8  `json:"poitype,omitempty"`
}

func (*Coordinates) SameAs

func (c *Coordinates) SameAs(c2 *Coordinates) bool

type Entity

type Entity struct {
	// Shared -----
	Mu            sync.Mutex
	Loc           *Location
	CurrentTarget *Entity
	TargetedBy    map[*Entity]bool
	Inventory     *Inventory
	Name          string
	Cash          int64
	Health        int
	Dead          bool
	SkillAcc      skills.Accuracy
	IsPlayer      bool
	LastLocation  Coordinates
	// Player -----
	PlayerID          uint64
	Bank              int64
	Gang              *Gang
	Hometown          string
	LastMove          int64
	LastSkill         map[string]int64
	LastAttack        int64
	PlayerKills       uint
	NpcKills          uint
	Reputation        int64
	UserId            uint64
	Rank              *Rank
	Client            *Client
	IsAdmin           bool
	AutoAttackEnabled bool
	AutoAttackType    ActionType
	SkillHide         skills.Hide
	SkillSearch       skills.Search
	SkillSnoop        skills.Snoop
	SkillTrack        skills.Track
	// NPC -----
	NpcID         string
	NpcCashReward int64
	NpcGender     Gender
	NpcType       NPCType
	NpcTitle      string
	NpcRepReward  int32
	NpcCommands   map[string]*Command
	NpcHostiles   map[string]bool
	// Shopping -----
	ShoppingWith map[*Entity]int64
}

func NewNPC

func NewNPC(npcType NPCType) *Entity

func (*Entity) ClearStock

func (n *Entity) ClearStock()

func (*Entity) Death

func (n *Entity) Death(killer *Entity)

func (*Entity) GangTag

func (p *Entity) GangTag() string

func (*Entity) NPCAttack

func (n *Entity) NPCAttack()

func (*Entity) NPCFindTarget

func (n *Entity) NPCFindTarget() (*Entity, bool)

func (*Entity) NPCGameFrame

func (n *Entity) NPCGameFrame() *responses.NPC

func (*Entity) NPCMove

func (n *Entity) NPCMove()

func (*Entity) NPCStartRoutines

func (n *Entity) NPCStartRoutines()

func (*Entity) OpenDrugDealer

func (e *Entity) OpenDrugDealer(c *Client)

func (*Entity) OpenDruggieMenu

func (e *Entity) OpenDruggieMenu(c *Client)

func (*Entity) PlayerGameFrame

func (p *Entity) PlayerGameFrame() *responses.Player

func (*Entity) PlayerGetBuildingCommand

func (p *Entity) PlayerGetBuildingCommand(cmdKey string) (*Command, bool)

func (*Entity) PlayerSendInventoryUpdate

func (p *Entity) PlayerSendInventoryUpdate()

func (*Entity) PlayerSendLocationUpdate

func (p *Entity) PlayerSendLocationUpdate()

func (*Entity) PlayerSendMapUpdate

func (p *Entity) PlayerSendMapUpdate()

func (*Entity) PlayerSendPlayerList

func (p *Entity) PlayerSendPlayerList()

func (*Entity) PlayerSendStatsUpdate

func (p *Entity) PlayerSendStatsUpdate()

func (*Entity) RandomiseGenderName

func (e *Entity) RandomiseGenderName()

func (*Entity) RemoveTargetLock

func (n *Entity) RemoveTargetLock()

func (*Entity) Restock

func (n *Entity) Restock()

func (*Entity) Save

func (e *Entity) Save()

func (*Entity) StartRoutines

func (e *Entity) StartRoutines()

func (*Entity) SyncDealerInventory

func (e *Entity) SyncDealerInventory(c *Client)

func (*Entity) SyncDruggieTrade

func (e *Entity) SyncDruggieTrade(c *Client)

type Game

type Game struct {
	DbConn       *sql.DB                        // DB Connection
	Logout       chan *Client                   // disconnected clients, gracefull logout
	Clients      map[*Client]bool               // connected clients
	Players      map[*Entity]bool               // connected clients
	Register     chan *Client                   // pending new connections
	GlobalEvents chan protoreflect.ProtoMessage // global chat
	NewsFlash    chan protoreflect.ProtoMessage // news flashes
	Movement     chan protoreflect.ProtoMessage // player movement events
	World        map[string]*City               // the game world
	// contains filtered or unexported fields
}

func NewGame

func NewGame(db *sql.DB) *Game

func (*Game) CheckNameTaken

func (g *Game) CheckNameTaken(w http.ResponseWriter, r *http.Request)

func (*Game) GetGang

func (g *Game) GetGang(gangId uint64) (*Gang, error)

func (*Game) GetPlayerClient

func (g *Game) GetPlayerClient(playerId uint64) *Entity

func (*Game) GetUserCharacter

func (g *Game) GetUserCharacter(userId uint64) (*Entity, *Coordinates, error)

func (*Game) HandleLogout

func (g *Game) HandleLogout(client *Client)

func (*Game) HandleRegistration

func (g *Game) HandleRegistration(w http.ResponseWriter, r *http.Request)

func (*Game) LoginPlayer

func (g *Game) LoginPlayer(c *Client, p *Entity, k *Coordinates)

func (*Game) RenderConsoleUI

func (g *Game) RenderConsoleUI()

func (*Game) Restock

func (g *Game) Restock()

func (*Game) Run

func (g *Game) Run()

func (*Game) Save

func (g *Game) Save()

func (*Game) SendMOTD

func (g *Game) SendMOTD(c *Client)

type Gang

type Gang struct {
	ID       uint64
	Name     string
	Tag      string
	LeaderID uint64
	Member   *Entity
}

func (*Gang) IsLeader

func (g *Gang) IsLeader(e *Entity) bool

func (*Gang) Save

func (g *Gang) Save() bool

type Gender

type Gender int8
const (
	GenderRandom Gender = 0
	GenderMale   Gender = 1
	GenderFemale Gender = 2
)

type IType

type IType uint8

type Inventory

type Inventory struct {
	Items     [settings.PlayerMaxInventory]*Item `json:"items"`
	Owner     *Entity
	Equipment map[IType]*Item
	Mu        sync.Mutex
}

func NewInventory

func NewInventory(owner *Entity) *Inventory

func (*Inventory) GameFrame

func (inv *Inventory) GameFrame() *responses.Inventory

func (*Inventory) HasItem

func (inv *Inventory) HasItem(templateId string) (bool, *Item)

func (*Inventory) HasRoom

func (inv *Inventory) HasRoom() bool

func (*Inventory) Info

func (i *Inventory) Info(itemId string)

type Item

type Item struct {
	ID           string
	Name         string
	Description  string
	TemplateName string
	ItemType     IType
	Condition    float32
	Amount       int32
	Price        uint32
	MinRep       int64
	BasePrice    uint
	MaxPrice     uint
	Damage       uint
	ArmorGuns    uint
	ArmorMelee   uint
	AmmoWear     float32
	Inventory    *Inventory
	Loc          *Location
	UseEffect    *ItemUseEffect
}

func NewItem

func NewItem(itemId string) (*Item, bool)

func (*Item) GameFrame

func (g *Item) GameFrame() *responses.Item

func (*Item) GetAmmoWear

func (i *Item) GetAmmoWear() float32

func (*Item) GetArmorGuns

func (i *Item) GetArmorGuns() uint

func (*Item) GetArmorMelee

func (i *Item) GetArmorMelee() uint

func (*Item) GetDamage

func (i *Item) GetDamage() uint

func (*Item) GetDescription

func (i *Item) GetDescription() string

func (*Item) GetItemStats

func (i *Item) GetItemStats() []string

func (*Item) GetItemType

func (i *Item) GetItemType() IType

func (*Item) GetMinRep

func (i *Item) GetMinRep() int64

func (*Item) GetName

func (i *Item) GetName() string

func (*Item) GetPrice

func (i *Item) GetPrice() uint32

func (*Item) GetQualitySuffix

func (i *Item) GetQualitySuffix() string

func (*Item) GetUseEffect

func (i *Item) GetUseEffect() *ItemUseEffect

func (*Item) InspectName

func (g *Item) InspectName() string

func (*Item) IsGear

func (i *Item) IsGear() bool

type ItemGameFrame

type ItemGameFrame struct {
	ID           string  `json:"id,omitempty"`
	Name         string  `json:"name,omitempty"`
	Description  string  `json:"description,omitempty"`
	Condition    float32 `json:"condition,omitempty"`
	ItemType     IType   `json:"type,omitempty"`
	Amount       int32   `json:"amount,omitempty"`
	Price        uint32  `json:"price,omitempty"`
	Damage       uint    `json:"damage,omitempty"`
	ArmorGuns    uint    `json:"armor_guns,omitempty"`
	ArmorMelee   uint    `json:"armor_melee,omitempty"`
	Equipped     bool    `json:"equipped,omitempty"`
	IsGear       bool    `json:"is_gear,omitempty"`
	HasUseEffect bool    `json:"has_use_effect,omitempty"`
}

type ItemMoved

type ItemMoved struct {
	Item   *Item
	By     string
	Player *Entity
}

type ItemSaveContainer

type ItemSaveContainer struct {
	ID           string  `json:"id"`
	TemplateName string  `json:"template_name"`
	Condition    float32 `json:"condition"`
	Amount       uint    `json:"amount"`
	Equipped     bool    `json:"equipped"`
}

type ItemTemplate

type ItemTemplate struct {
	Name        string
	Description string
	ItemType    IType
	Uses        bool
	BasePrice   uint
	MaxPrice    uint
	MinRep      int64
	Damage      uint
	Amount      uint
	AmmoWear    float32
	ArmorGuns   uint
	ArmorMelee  uint
	UseEffect   string
}

type ItemUseEffect

type ItemUseEffect struct {
	Use func(*Client, *Item, int)
}

type KeyFloatValue

type KeyFloatValue struct {
	Key   string  `json:"key"`
	Value float32 `json:"value"`
}

type KeyIntValue

type KeyIntValue struct {
	Key   string `json:"key"`
	Value int32  `json:"value"`
}

type KeyStringValue

type KeyStringValue struct {
	Key   string `json:"key"`
	Value string `json:"value"`
}

type Location

type Location struct {
	City        *City
	Description string
	Coords      Coordinates
	Players     map[*Client]bool
	Npcs        map[*Entity]bool
	Items       map[*Item]bool
	PlayerJoin  chan *Client
	NpcJoin     chan *Entity
	AddItem     chan *ItemMoved
	RemoveItem  chan *ItemMoved
	Events      chan *ClientResponse
	Respawn     chan CombatAction
	Buildings   []*Building
	// contains filtered or unexported fields
}

func CreateLocation

func CreateLocation(c *City, north int, east int) *Location

type Movement

type Movement struct {
	Origin      Coordinates `json:"origin,omitempty"`
	Destination Coordinates `json:"destination,omitempty"`
}

type NPCGameFrame

type NPCGameFrame struct {
	Name    string  `json:"name"`
	NpcType NPCType `json:"type"`
	Rank    string  `json:"rank"`
	Health  int     `json:"health"`
	Gender  Gender  `json:"gender"`
}

type NPCType

type NPCType int16
const (
	DrugDealer       NPCType = 0
	DrugAddict       NPCType = 1
	Homeless         NPCType = 2
	Tweaker          NPCType = 3
	Bouncer          NPCType = 4
	Busker           NPCType = 5
	StreetVendor     NPCType = 6
	StreetGangMember NPCType = 7
	Tourist          NPCType = 8
	Activist         NPCType = 9
	PoliceOfficer    NPCType = 10
	BeatCop          NPCType = 11
	DeliveryDriver   NPCType = 12
)

type NpcTemplate

type NpcTemplate struct {
	NpcType   NPCType
	Title     string
	Rep       int32
	Cash      int64
	Gender    Gender
	Health    int
	SkillAcc  float32
	Equipment map[IType]string
	Inventory []string
	// contains filtered or unexported fields
}

type PlayerGameFrame

type PlayerGameFrame struct {
	Id      uint64 `json:"id"`
	Name    string `json:"name"`
	Rank    string `json:"rank"`
	GangTag string `json:"gang_tag"`
}

type Rank

type Rank struct {
	Name     string `json:"name"`
	MinRep   int64  `json:"rep"`
	NextRank *Rank
	PrevRank *Rank
}

func GetRank

func GetRank(rep int64) *Rank

type RegistrationBody

type RegistrationBody struct {
	Email    string
	Password string
}

type RegistrationResponse

type RegistrationResponse struct {
	Error   bool     `json:"error"`
	Message string   `json:"message"`
	Fields  []string `json:"fields"`
}

type StockItem

type StockItem struct {
	TemplateId string
	Amount     int32
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL