Showing preview only (622K chars total). Download the full file or copy to clipboard to get everything.
Repository: jsuarez5341/neural-mmo
Branch: 2.1
Commit: 98b6bbdaa4aa
Files: 121
Total size: 586.7 KB
Directory structure:
gitextract_fsthiw8v/
├── .gitattributes
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ ├── documentation.md
│ │ ├── enhancement.md
│ │ └── feature_request.md
│ └── workflows/
│ └── pylint-test.yml
├── .gitignore
├── .pylintrc
├── LICENSE
├── MANIFEST.in
├── README.md
├── nmmo/
│ ├── __init__.py
│ ├── core/
│ │ ├── __init__.py
│ │ ├── action.py
│ │ ├── agent.py
│ │ ├── config.py
│ │ ├── env.py
│ │ ├── game_api.py
│ │ ├── map.py
│ │ ├── observation.py
│ │ ├── realm.py
│ │ ├── terrain.py
│ │ └── tile.py
│ ├── datastore/
│ │ ├── __init__.py
│ │ ├── datastore.py
│ │ ├── id_allocator.py
│ │ ├── numpy_datastore.py
│ │ └── serialized.py
│ ├── entity/
│ │ ├── __init__.py
│ │ ├── entity.py
│ │ ├── entity_manager.py
│ │ ├── npc.py
│ │ ├── npc_manager.py
│ │ └── player.py
│ ├── lib/
│ │ ├── __init__.py
│ │ ├── astar.py
│ │ ├── colors.py
│ │ ├── cython_helper.pyx
│ │ ├── event_code.py
│ │ ├── event_log.py
│ │ ├── material.py
│ │ ├── seeding.py
│ │ ├── spawn.py
│ │ ├── team_helper.py
│ │ ├── utils.py
│ │ └── vec_noise.py
│ ├── minigames/
│ │ ├── __init__.py
│ │ ├── center_race.py
│ │ ├── comm_together.py
│ │ ├── king_hill.py
│ │ ├── radio_raid.py
│ │ └── sandwich.py
│ ├── render/
│ │ ├── __init__.py
│ │ ├── overlay.py
│ │ ├── render_client.py
│ │ └── render_utils.py
│ ├── systems/
│ │ ├── __init__.py
│ │ ├── combat.py
│ │ ├── droptable.py
│ │ ├── exchange.py
│ │ ├── inventory.py
│ │ ├── item.py
│ │ └── skill.py
│ ├── task/
│ │ ├── __init__.py
│ │ ├── base_predicates.py
│ │ ├── game_state.py
│ │ ├── group.py
│ │ ├── predicate_api.py
│ │ ├── task_api.py
│ │ └── task_spec.py
│ └── version.py
├── pyproject.toml
├── scripted/
│ ├── __init__.py
│ ├── attack.py
│ ├── baselines.py
│ └── move.py
├── setup.py
├── tests/
│ ├── __init__.py
│ ├── action/
│ │ ├── test_ammo_use.py
│ │ ├── test_destroy_give_gold.py
│ │ ├── test_monkey_action.py
│ │ └── test_sell_buy.py
│ ├── conftest.py
│ ├── core/
│ │ ├── test_config.py
│ │ ├── test_cython_masks.py
│ │ ├── test_entity.py
│ │ ├── test_env.py
│ │ ├── test_game_api.py
│ │ ├── test_gym_obs_spaces.py
│ │ ├── test_map_generation.py
│ │ ├── test_observation_tile.py
│ │ ├── test_tile_property.py
│ │ └── test_tile_seize.py
│ ├── datastore/
│ │ ├── test_datastore.py
│ │ ├── test_id_allocator.py
│ │ ├── test_numpy_datastore.py
│ │ └── test_serialized.py
│ ├── render/
│ │ ├── test_load_replay.py
│ │ └── test_render_save.py
│ ├── systems/
│ │ ├── test_exchange.py
│ │ ├── test_item.py
│ │ └── test_skill_level.py
│ ├── task/
│ │ ├── sample_curriculum.pkl
│ │ ├── test_demo_task_creation.py
│ │ ├── test_manual_curriculum.py
│ │ ├── test_predicates.py
│ │ ├── test_sample_task_from_file.py
│ │ ├── test_task_api.py
│ │ └── test_task_system_perf.py
│ ├── test_death_fog.py
│ ├── test_determinism.py
│ ├── test_eventlog.py
│ ├── test_memory_usage.py
│ ├── test_mini_games.py
│ ├── test_performance.py
│ ├── test_pettingzoo.py
│ ├── test_rollout.py
│ └── testhelpers.py
└── utils/
├── git-pr.sh
├── pre-git-check.sh
└── run-perf-tests.sh
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
neural_mmo/_version.py export-subst
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Report a bug with the Neural MMO environment or Unity3D Embyr client
title: "[Bug Report]"
labels: ''
assignees: ''
---
Fill out as much of the below as you can. A partial bug report is better than no bug report. After submitting, link your issue on our [Discord](https://discord.gg/BkMmFUC) #support channel
**OS:** Your operating system
**Description:** What's wrong
**Repro:** How do we reproduce the issue? Minimal scripts are best. Instructions are acceptable. "I don't know" is valid.
================================================
FILE: .github/ISSUE_TEMPLATE/documentation.md
================================================
---
name: Documentation
about: Report problems with the documentation
title: "[Docs]"
labels: ''
assignees: ''
---
One of:
**Insufficient**: Something is documented, but the current documentation is inadequate
**Missing**: Something is undocumented. Note that most internal functions should be considered self-documenting -- consider submitting an enhancement report for refactoring if they are not.
**Other**: Something else. We will update this template to include your problem category afterwards.
================================================
FILE: .github/ISSUE_TEMPLATE/enhancement.md
================================================
---
name: Enhancement
about: Suggest an improvement to an API or a refactorization of existing code for
better efficiency or clarity
title: "[Enhancement]"
labels: ''
assignees: ''
---
This feature template is mostly used by the developers to track ongoing tasks, but users are also free to suggest additional enhancements or submit PRs solving existing ones. At the current scale, you should come chat with us on the Discord #development channel before writing one of these.
Try to match one of the templates below. If you can't, use the "other" template for now and we'll add a new template matching your issue afterwards.
**Dead code**: A piece of code is unused and should be deleted. The most common case for a dead code report occurs when we have replaced an older, clunkier routine but have neglected to delete the original. Check to make sure that you are not reporting a util function or paused-development feature before submitting.
**Confusing code**: A piece of code is difficult to parse and should be refactored or at least commented. These are subjective, but we take them seriously. Neural MMO is designed to be hackable -- the internals matter just as much as the user API.
**Poor performance**: A function or subroutine is slow. Describe cases in which this functionality becomes a bottleneck and submit timing data.
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Request a feature for the Neural MMO environment of Unity3D Embyr client
title: "[Feature Request]"
labels: ''
assignees: ''
---
Eventually, feature requests will be treated as a scrum board for open-source contributors. At the current scale, you should come chat with us on the [Discord](https://discord.gg/BkMmFUC) #development channel before writing one of these.
**I am trying to:** Describe your use case. What is the end result you would like to achieve?
**It is hard/impossible because:" Is this a core missing feature? Is Neural MMO structured in a way that makes what you are trying to do unnecessarily hard? Is documentation missing or confusing?
**The solution should look like:** Describe your ideal solution -- a requirement, an API, a restructuring, additional documentation, etc.
================================================
FILE: .github/workflows/pylint-test.yml
================================================
name: pylint-test
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
# Randomly hitting TypeError: object int can't be used in 'await' expression in 3.11
# So, excluding 3.11 for now
python-version: ["3.8", "3.9", "3.10"]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip setuptools wheel cython
pip install .
python setup.py build_ext --inplace
- name: Running unit tests
run: pytest
- name: Analysing the code with pylint
run: pylint --recursive=y nmmo tests
- name: Looking for xcxc, just in case
run: |
if grep -r --include='*.py' 'xcxc'; then
echo "Found xcxc in the code. Please check the file."
exit 1
fi
================================================
FILE: .gitignore
================================================
# Game maps
maps/
*.swp
runs/*
wandb/*
# local replay file from test_render_save.py
tests/replay_local*.pickle
replay*
eval*
.vscode
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions, cython
*.so
*.c
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
#lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.*venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintainted in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
profile.run
================================================
FILE: .pylintrc
================================================
[MESSAGES CONTROL]
disable=W0511, # TODO/FIXME
W0105, # string is used as a statement
C0114, # missing module docstring
C0115, # missing class docstring
C0116, # missing function docstring
W0221, # arguments differ from overridden method
C0415, # import outside toplevel
E0611, # no name in module
R0901, # too many ancestors
R0902, # too many instance attributes
R0903, # too few public methods
R0911, # too many return statements
R0912, # too many branches
R0913, # too many arguments
R0914, # too many local variables
R0914, # too many local variables
R0915, # too many statements
R0401, # cyclic import
[INDENTATION]
indent-string=' '
[MASTER]
good-names-rgxs=^[_a-zA-Z][_a-z0-9]?$ # whitelist short variables
known-third-party=ordered_set,numpy,gym,pettingzoo,vec_noise,imageio,scipy,tqdm
load-plugins=pylint.extensions.bad_builtin
[BASIC]
bad-functions=print # checks if these functions are used
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2019 OpenAI, 2020 Joseph Suarez
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: MANIFEST.in
================================================
global-include *.pyx
include nmmo/resource/*
================================================
FILE: README.md
================================================

#  Welcome to the Platform!
[](https://badge.fury.io/py/nmmo)
[](https://discord.gg/BkMmFUC)
[](https://twitter.com/jsuarez5341)
Neural MMO is a massively multiagent environment for artificial intelligence research inspired by Massively Multiplayer Online (MMO) role-playing games. [Documentation](https://neuralmmo.github.io "Neural MMO Documentation") is hosted by github.io.
================================================
FILE: nmmo/__init__.py
================================================
import logging
from .version import __version__
from .lib import material, spawn
from .render.overlay import Overlay, OverlayRegistry
from .core import config, agent, action
from .core.action import Action
from .core.agent import Agent, Scripted
from .core.env import Env
from .core.terrain import MapGenerator, Terrain
MOTD = rf''' ___ ___ ___ ___
/__/\ /__/\ /__/\ / /\ Version {__version__:<8}
\ \:\ | |::\ | |::\ / /::\
\ \:\ | |:|:\ | |:|:\ / /:/\:\ An open source
_____\__\:\ __|__|:|\:\ __|__|:|\:\ / /:/ \:\ project originally
/__/::::::::\ /__/::::| \:\ /__/::::| \:\ /__/:/ \__\:\ founded by Joseph Suarez
\ \:\~~\~~\/ \ \:\~~\__\/ \ \:\~~\__\/ \ \:\ / /:/ and formalized at OpenAI
\ \:\ ~~~ \ \:\ \ \:\ \ \:\ /:/
\ \:\ \ \:\ \ \:\ \ \:\/:/ Now developed and
\ \:\ \ \:\ \ \:\ \ \::/ maintained at MIT in
\__\/ \__\/ \__\/ \__\/ Phillip Isola's lab '''
__all__ = ['Env', 'config', 'agent', 'Agent', 'Scripted', 'MapGenerator', 'Terrain',
'action', 'Action', 'material', 'spawn',
'Overlay', 'OverlayRegistry']
try:
__all__.append('OpenSkillRating')
except RuntimeError:
logging.error('Warning: OpenSkill not installed. Ignore if you do not need this feature')
================================================
FILE: nmmo/core/__init__.py
================================================
================================================
FILE: nmmo/core/action.py
================================================
# pylint: disable=no-method-argument,unused-argument,no-self-argument,no-member
from enum import Enum, auto
import numpy as np
from nmmo.lib import utils
from nmmo.lib.utils import staticproperty
from nmmo.systems.item import Stack
from nmmo.lib.event_code import EventCode
from nmmo.core.observation import Observation
class NodeType(Enum):
#Tree edges
STATIC = auto() #Traverses all edges without decisions
SELECTION = auto() #Picks an edge to follow
#Executable actions
ACTION = auto() #No arguments
CONSTANT = auto() #Constant argument
VARIABLE = auto() #Variable argument
class Node(metaclass=utils.IterableNameComparable):
@classmethod
def init(cls, config):
# noop_action is used in some of the N() methods
cls.noop_action = 1 if config.PROVIDE_NOOP_ACTION_TARGET else 0
@staticproperty
def edges():
return []
#Fill these in
@staticproperty
def priority():
return None
@staticproperty
def type():
return None
@staticproperty
def leaf():
return False
@classmethod
def N(cls, config):
return len(cls.edges)
def deserialize(realm, entity, index: int, obs: Observation):
return index
class Fixed:
pass
#ActionRoot
class Action(Node):
nodeType = NodeType.SELECTION
hooked = False
@classmethod
def init(cls, config):
# Sets up serialization domain
if Action.hooked:
return
Action.hooked = True
#Called upon module import (see bottom of file)
#Sets up serialization domain
def hook(config):
idx = 0
arguments = []
for action in Action.edges(config):
action.init(config)
for args in action.edges: # pylint: disable=not-an-iterable
args.init(config)
if not "edges" in args.__dict__:
continue
for arg in args.edges:
arguments.append(arg)
arg.serial = tuple([idx])
arg.idx = idx
idx += 1
Action.arguments = arguments
@staticproperty
def n():
return len(Action.arguments)
# pylint: disable=invalid-overridden-method
@classmethod
def edges(cls, config):
"""List of valid actions"""
edges = [Move]
if config.COMBAT_SYSTEM_ENABLED:
edges.append(Attack)
if config.ITEM_SYSTEM_ENABLED:
edges += [Use, Give, Destroy]
if config.EXCHANGE_SYSTEM_ENABLED:
edges += [Buy, Sell, GiveGold]
if config.COMMUNICATION_SYSTEM_ENABLED:
edges.append(Comm)
return edges
class Move(Node):
priority = 60
nodeType = NodeType.SELECTION
def call(realm, entity, direction):
if direction is None:
return
assert entity.alive, "Dead entity cannot act"
assert realm.map.is_valid_pos(*entity.pos), "Invalid entity position"
r, c = entity.pos
ent_id = entity.ent_id
entity.history.last_pos = (r, c)
r_delta, c_delta = direction.delta
r_new, c_new = r+r_delta, c+c_delta
if not realm.map.is_valid_pos(r_new, c_new) or \
realm.map.tiles[r_new, c_new].impassible:
return
# ALLOW_MOVE_INTO_OCCUPIED_TILE only applies to players, NOT npcs
if entity.is_player and not realm.config.ALLOW_MOVE_INTO_OCCUPIED_TILE and \
realm.map.tiles[r_new, c_new].occupied:
return
if entity.status.freeze > 0:
return
entity.set_pos(r_new, c_new)
realm.map.tiles[r, c].remove_entity(ent_id)
realm.map.tiles[r_new, c_new].add_entity(entity)
# exploration record keeping. moved from entity.py, History.update()
progress_to_center = realm.map.dist_border_center -\
utils.linf_single(realm.map.center_coord, (r_new, c_new))
if progress_to_center > entity.history.exploration:
entity.history.exploration = progress_to_center
if entity.is_player:
realm.event_log.record(EventCode.GO_FARTHEST, entity,
distance=progress_to_center)
# CHECK ME: material.Impassible includes void, so this line is not reachable
# Does this belong to Entity/Player.update()?
if realm.map.tiles[r_new, c_new].void:
entity.receive_damage(None, entity.resources.health.val)
@staticproperty
def edges():
return [Direction]
@staticproperty
def leaf():
return True
def enabled(config):
return True
class Direction(Node):
argType = Fixed
@staticproperty
def edges():
return [North, South, East, West, Stay]
def deserialize(realm, entity, index: int, obs):
return deserialize_fixed_arg(Direction, index)
# a quick helper function
def deserialize_fixed_arg(arg, index):
if isinstance(index, (int, np.int64)):
if index < 0:
return None # so that the action will be discarded
val = min(index, len(arg.edges)-1)
return arg.edges[val]
# if index is not int, it's probably already deserialized
if index not in arg.edges:
return None # so that the action will be discarded
return index
class North(Node):
delta = (-1, 0)
class South(Node):
delta = (1, 0)
class East(Node):
delta = (0, 1)
class West(Node):
delta = (0, -1)
class Stay(Node):
delta = (0, 0)
class Attack(Node):
priority = 50
nodeType = NodeType.SELECTION
@staticproperty
def n():
return 3
@staticproperty
def edges():
return [Style, Target]
@staticproperty
def leaf():
return True
def enabled(config):
return config.COMBAT_SYSTEM_ENABLED
def in_range(entity, stim, config, N):
R, C = stim.shape
R, C = R//2, C//2
rets = set([entity])
for r in range(R-N, R+N+1):
for c in range(C-N, C+N+1):
for e in stim[r, c].entities.values():
rets.add(e)
rets = list(rets)
return rets
def call(realm, entity, style, target):
if style is None or target is None:
return None
assert entity.alive, "Dead entity cannot act"
config = realm.config
if entity.is_player and not config.COMBAT_SYSTEM_ENABLED:
return None
# Testing a spawn immunity against old agents to avoid spawn camping
immunity = config.COMBAT_SPAWN_IMMUNITY
if entity.is_player and target.is_player and \
target.history.time_alive < immunity:
return None
#Check if self targeted or target already dead
if entity.ent_id == target.ent_id or not target.alive:
return None
#Can't attack out of range
if utils.linf_single(entity.pos, target.pos) > style.attack_range(config):
return None
#Execute attack
entity.history.attack = {}
entity.history.attack["target"] = target.ent_id
entity.history.attack["style"] = style.__name__
target.attacker = entity
target.attacker_id.update(entity.ent_id)
from nmmo.systems import combat
dmg = combat.attack(realm, entity, target, style.skill)
# record the combat tick for both entities
# players and npcs both have latest_combat_tick in EntityState
for ent in [entity, target]:
ent.latest_combat_tick.update(realm.tick + 1) # because the tick is about to increment
return dmg
class Style(Node):
argType = Fixed
@staticproperty
def edges():
return [Melee, Range, Mage]
def deserialize(realm, entity, index: int, obs):
return deserialize_fixed_arg(Style, index)
class Target(Node):
argType = None
@classmethod
def N(cls, config):
return config.PLAYER_N_OBS + cls.noop_action
def deserialize(realm, entity, index: int, obs: Observation):
if index >= len(obs.entities.ids):
return None
return realm.entity_or_none(obs.entities.ids[index])
class Melee(Node):
nodeType = NodeType.ACTION
freeze=False
def attack_range(config):
return config.COMBAT_MELEE_REACH
def skill(entity):
return entity.skills.melee
class Range(Node):
nodeType = NodeType.ACTION
freeze=False
def attack_range(config):
return config.COMBAT_RANGE_REACH
def skill(entity):
return entity.skills.range
class Mage(Node):
nodeType = NodeType.ACTION
freeze=False
def attack_range(config):
return config.COMBAT_MAGE_REACH
def skill(entity):
return entity.skills.mage
class InventoryItem(Node):
argType = None
@classmethod
def N(cls, config):
return config.INVENTORY_N_OBS + cls.noop_action
def deserialize(realm, entity, index: int, obs: Observation):
if index >= len(obs.inventory.ids):
return None
return realm.items.get(obs.inventory.ids[index])
class Use(Node):
priority = 10
@staticproperty
def edges():
return [InventoryItem]
def enabled(config):
return config.ITEM_SYSTEM_ENABLED
def call(realm, entity, item):
if item is None or item.owner_id.val != entity.ent_id:
return
assert entity.alive, "Dead entity cannot act"
assert entity.is_player, "Npcs cannot use an item"
assert item.quantity.val > 0, "Item quantity cannot be 0" # indicates item leak
if not realm.config.ITEM_SYSTEM_ENABLED:
return
if item not in entity.inventory:
return
if entity.in_combat: # player cannot use item during combat
return
# cannot use listed items or items that have higher level
if item.listed_price.val > 0 or item.level_gt(entity):
return
item.use(entity)
class Destroy(Node):
priority = 40
@staticproperty
def edges():
return [InventoryItem]
def enabled(config):
return config.ITEM_SYSTEM_ENABLED
def call(realm, entity, item):
if item is None or item.owner_id.val != entity.ent_id:
return
assert entity.alive, "Dead entity cannot act"
assert entity.is_player, "Npcs cannot destroy an item"
assert item.quantity.val > 0, "Item quantity cannot be 0" # indicates item leak
if not realm.config.ITEM_SYSTEM_ENABLED:
return
if item not in entity.inventory:
return
if item.equipped.val: # cannot destroy equipped item
return
if entity.in_combat: # player cannot destroy item during combat
return
item.destroy()
realm.event_log.record(EventCode.DESTROY_ITEM, entity)
class Give(Node):
priority = 30
@staticproperty
def edges():
return [InventoryItem, Target]
def enabled(config):
return config.ITEM_SYSTEM_ENABLED
def call(realm, entity, item, target):
if item is None or item.owner_id.val != entity.ent_id or target is None:
return
assert entity.alive, "Dead entity cannot act"
assert entity.is_player, "Npcs cannot give an item"
assert item.quantity.val > 0, "Item quantity cannot be 0" # indicates item leak
config = realm.config
if not config.ITEM_SYSTEM_ENABLED:
return
if not (target.is_player and target.alive):
return
if item not in entity.inventory:
return
# cannot give the equipped or listed item
if item.equipped.val or item.listed_price.val:
return
if entity.in_combat: # player cannot give item during combat
return
if not (config.ITEM_ALLOW_GIFT and
entity.ent_id != target.ent_id and # but not self
target.is_player):
return
# NOTE: allow give within the visual range
if utils.linf_single(entity.pos, target.pos) > config.PLAYER_VISION_RADIUS:
return
if not target.inventory.space:
# receiver inventory is full - see if it has an ammo stack with the same sig
if isinstance(item, Stack):
if not target.inventory.has_stack(item.signature):
# no ammo stack with the same signature, so cannot give
return
else: # no space, and item is not ammo stack, so cannot give
return
entity.inventory.remove(item)
target.inventory.receive(item)
realm.event_log.record(EventCode.GIVE_ITEM, entity)
class GiveGold(Node):
priority = 30
@staticproperty
def edges():
# CHECK ME: for now using Price to indicate the gold amount to give
return [Price, Target]
def enabled(config):
return config.EXCHANGE_SYSTEM_ENABLED
def call(realm, entity, amount, target):
if amount is None or target is None:
return
assert entity.alive, "Dead entity cannot act"
assert entity.is_player, "Npcs cannot give gold"
config = realm.config
if not config.EXCHANGE_SYSTEM_ENABLED:
return
if not (target.is_player and target.alive):
return
if entity.in_combat: # player cannot give gold during combat
return
if not (config.ITEM_ALLOW_GIFT and
entity.ent_id != target.ent_id and # but not self
target.is_player):
return
# NOTE: allow give within the visual range
if utils.linf_single(entity.pos, target.pos) > config.PLAYER_VISION_RADIUS:
return
if not isinstance(amount, int):
amount = amount.val
if amount > entity.gold.val: # no gold to give
return
entity.gold.decrement(amount)
target.gold.increment(amount)
realm.event_log.record(EventCode.GIVE_GOLD, entity)
class MarketItem(Node):
argType = None
@classmethod
def N(cls, config):
return config.MARKET_N_OBS + cls.noop_action
def deserialize(realm, entity, index: int, obs: Observation):
if index >= len(obs.market.ids):
return None
return realm.items.get(obs.market.ids[index])
class Buy(Node):
priority = 20
argType = Fixed
@staticproperty
def edges():
return [MarketItem]
def enabled(config):
return config.EXCHANGE_SYSTEM_ENABLED
def call(realm, entity, item):
if item is None or item.owner_id.val == 0:
return
assert entity.alive, "Dead entity cannot act"
assert entity.is_player, "Npcs cannot buy an item"
assert item.quantity.val > 0, "Item quantity cannot be 0" # indicates item leak
assert item.equipped.val == 0, "Listed item must not be equipped"
if not realm.config.EXCHANGE_SYSTEM_ENABLED:
return
if entity.gold.val < item.listed_price.val: # not enough money
return
if entity.ent_id == item.owner_id.val: # cannot buy own item
return
if entity.in_combat: # player cannot buy item during combat
return
if not entity.inventory.space:
# buyer inventory is full - see if it has an ammo stack with the same sig
if isinstance(item, Stack):
if not entity.inventory.has_stack(item.signature):
# no ammo stack with the same signature, so cannot give
return
else: # no space, and item is not ammo stack, so cannot give
return
# one can try to buy, but the listing might have gone (perhaps bought by other)
realm.exchange.buy(entity, item)
class Sell(Node):
priority = 70
argType = Fixed
@staticproperty
def edges():
return [InventoryItem, Price]
def enabled(config):
return config.EXCHANGE_SYSTEM_ENABLED
def call(realm, entity, item, price):
if item is None or item.owner_id.val != entity.ent_id or price is None:
return
assert entity.alive, "Dead entity cannot act"
assert entity.is_player, "Npcs cannot sell an item"
assert item.quantity.val > 0, "Item quantity cannot be 0" # indicates item leak
if not realm.config.EXCHANGE_SYSTEM_ENABLED:
return
if item not in entity.inventory:
return
if entity.in_combat: # player cannot sell item during combat
return
# cannot sell the equipped or listed item
if item.equipped.val or item.listed_price.val:
return
if not isinstance(price, int):
price = price.val
if not price > 0:
return
realm.exchange.sell(entity, item, price, realm.tick)
def init_discrete(values):
classes = []
for i in values:
name = f"Discrete_{i}"
cls = type(name, (object,), {"val": i})
classes.append(cls)
return classes
class Price(Node):
argType = Fixed
@classmethod
def init(cls, config):
# gold should be > 0
cls.price_range = range(1, config.PRICE_N_OBS+1)
Price.classes = init_discrete(cls.price_range)
@classmethod
def index(cls, price):
try:
return cls.price_range.index(price)
except ValueError:
# use the max price, which is config.PRICE_N_OBS
return len(cls.price_range) - 1
@staticproperty
def edges():
return Price.classes
def deserialize(realm, entity, index: int, obs):
return deserialize_fixed_arg(Price, index)
class Token(Node):
argType = Fixed
@classmethod
def init(cls, config):
Token.classes = init_discrete(range(1, config.COMMUNICATION_NUM_TOKENS+1))
@staticproperty
def edges():
return Token.classes
def deserialize(realm, entity, index: int, obs):
return deserialize_fixed_arg(Token, index)
class Comm(Node):
argType = Fixed
priority = 99
@staticproperty
def edges():
return [Token]
def enabled(config):
return config.COMMUNICATION_SYSTEM_ENABLED
def call(realm, entity, token):
if token is None:
return
entity.message.update(token.val)
#TODO: Solve AGI
class BecomeSkynet:
pass
================================================
FILE: nmmo/core/agent.py
================================================
class Agent:
policy = 'Neural'
def __init__(self, config, idx):
'''Base class for agents
Args:
config: A Config object
idx: Unique AgentID int
'''
self.config = config
self.iden = idx
self._np_random = None
def __call__(self, obs):
'''Used by scripted agents to compute actions. Override in subclasses.
Args:
obs: Agent observation provided by the environment
'''
def set_rng(self, np_random):
'''Set the random number generator for the agent for reproducibility
Args:
np_random: A numpy random.Generator object
'''
self._np_random = np_random
class Scripted(Agent):
'''Base class for scripted agents'''
policy = 'Scripted'
================================================
FILE: nmmo/core/config.py
================================================
# pylint: disable=invalid-name
from __future__ import annotations
import os
import sys
import logging
import re
import nmmo
from nmmo.core.agent import Agent
from nmmo.core.terrain import MapGenerator
from nmmo.lib import utils, material, spawn
CONFIG_ATTR_PATTERN = r"^[A-Z_]+$"
GAME_SYSTEMS = ["TERRAIN", "RESOURCE", "COMBAT", "NPC", "PROGRESSION", "ITEM",
"EQUIPMENT", "PROFESSION", "EXCHANGE", "COMMUNICATION"]
# These attributes are critical for trainer and must not change from the initial values
OBS_ATTRS = set(["MAX_HORIZON", "PLAYER_N", "MAP_N_OBS", "PLAYER_N_OBS", "TASK_EMBED_DIM",
"ITEM_INVENTORY_CAPACITY", "MARKET_N_OBS", "PRICE_N_OBS",
"COMMUNICATION_NUM_TOKENS", "COMMUNICATION_N_OBS", "PROVIDE_ACTION_TARGETS",
"PROVIDE_DEATH_FOG_OBS", "PROVIDE_NOOP_ACTION_TARGET"])
IMMUTABLE_ATTRS = set(["USE_CYTHON", "CURRICULUM_FILE_PATH", "PLAYER_VISION_RADIUS", "MAP_SIZE",
"PLAYER_BASE_HEALTH", "RESOURCE_BASE", "PROGRESSION_LEVEL_MAX"])
class Template(metaclass=utils.StaticIterable):
def __init__(self):
self._data = {}
cls = type(self)
# Set defaults from static properties
for attr in dir(cls):
val = getattr(cls, attr)
if re.match(CONFIG_ATTR_PATTERN, attr) and not isinstance(val, property):
self._data[attr] = val
def override(self, **kwargs):
for k, v in kwargs.items():
err = f'CLI argument: {k} is not a Config property'
assert hasattr(self, k), err
self.set(k, v)
def set(self, k, v):
if not isinstance(v, property):
try:
setattr(self, k, v)
except AttributeError:
logging.error('Cannot set attribute: %s to %s', str(k), str(v))
sys.exit()
self._data[k] = v
# pylint: disable=bad-builtin
def print(self):
key_len = 0
for k in self._data:
key_len = max(key_len, len(k))
print('Configuration')
for k, v in self._data.items():
print(f' {k:{key_len}s}: {v}')
def items(self):
return self._data.items()
def __iter__(self):
for k in self._data:
yield k
def keys(self):
return self._data.keys()
def values(self):
return self._data.values()
def validate(config):
err = 'config.Config is a base class. Use config.{Small, Medium Large}'''
assert isinstance(config, Config), err
assert config.HORIZON < config.MAX_HORIZON, 'HORIZON must be <= MAX_HORIZON'
if not config.TERRAIN_SYSTEM_ENABLED:
err = 'Invalid Config: {} requires Terrain'
assert not config.RESOURCE_SYSTEM_ENABLED, err.format('Resource')
assert not config.PROFESSION_SYSTEM_ENABLED, err.format('Profession')
if not config.COMBAT_SYSTEM_ENABLED:
err = 'Invalid Config: {} requires Combat'
assert not config.NPC_SYSTEM_ENABLED, err.format('NPC')
if not config.ITEM_SYSTEM_ENABLED:
err = 'Invalid Config: {} requires Inventory'
assert not config.EQUIPMENT_SYSTEM_ENABLED, err.format('Equipment')
assert not config.PROFESSION_SYSTEM_ENABLED, err.format('Profession')
assert not config.EXCHANGE_SYSTEM_ENABLED, err.format('Exchange')
class Config(Template):
'''An environment configuration object'''
env_initialized = False
def __init__(self):
super().__init__()
self._attr_to_reset = []
# TODO: Come up with a better way
# to resolve mixin MRO conflicts
for system in GAME_SYSTEMS:
if not hasattr(self, f'{system}_SYSTEM_ENABLED'):
self.set(f'{system}_SYSTEM_ENABLED', False)
if __debug__:
validate(self)
deprecated_attrs = [
'NENT', 'NPOP', 'AGENTS', 'NMAPS', 'FORCE_MAP_GENERATION', 'SPAWN']
for attr in deprecated_attrs:
assert not hasattr(self, attr), f'{attr} has been deprecated or renamed'
@property
def original(self):
return self._data
def reset(self):
'''Reset all attributes changed during the episode'''
for attr in self._attr_to_reset:
setattr(self, attr, self.original[attr])
def set(self, k, v):
assert self.env_initialized is False, 'Cannot set config attr after env init'
super().set(k, v)
def set_for_episode(self, k, v):
'''Set a config property for the current episode'''
assert hasattr(self, k), f'Invalid config property: {k}'
assert k not in OBS_ATTRS, f'Cannot change OBS config {k} during the episode'
assert k not in IMMUTABLE_ATTRS, f'Cannot change {k} during the episode'
# Cannot turn on a game system that was not enabled when the env was created
if k.endswith('_SYSTEM_ENABLED') and self._data[k] is False and v is True:
raise AssertionError(f'Cannot turn on {k} because it was not enabled during env init')
# Change only the attribute and keep the original value in the data dict
setattr(self, k, v)
self._attr_to_reset.append(k)
@property
def enabled_systems(self):
'''Return a list of the enabled systems from Env.__init__()'''
return [k[:-len('_SYSTEM_ENABLED')]
for k, v in self._data.items() if k.endswith('_SYSTEM_ENABLED') and v is True]
@property
def system_states(self):
'''Return a one-hot encoding of each system enabled/disabled,
which can be used as an observation and changed from episode to episode'''
return [int(getattr(self, f'{system}_SYSTEM_ENABLED')) for system in GAME_SYSTEMS]
def are_systems_enabled(self, systems): # systems is a list of strings
'''Check if all provided systems are enabled'''
return all(s.upper() in self.enabled_systems for s in systems)
def toggle_systems(self, target_systems): # systems is a list of strings
'''Activate only the provided game systems and turn off the others'''
target_systems = [s.upper() for s in target_systems]
for system in target_systems:
assert system in self.enabled_systems, f'Invalid game system: {system}'
self.set_for_episode(f'{system}_SYSTEM_ENABLED', True)
for system in self.enabled_systems:
if system not in target_systems:
self.set_for_episode(f'{system}_SYSTEM_ENABLED', False)
############################################################################
### Meta-Parameters
PLAYERS = [Agent]
'''Player classes from which to spawn'''
@property
def PLAYER_POLICIES(self):
'''Number of player policies'''
return len(self.PLAYERS)
PLAYER_N = None
'''Maximum number of players spawnable in the environment'''
@property
def POSSIBLE_AGENTS(self):
'''List of possible agents to spawn'''
return list(range(1, self.PLAYER_N + 1))
# TODO: CHECK if there could be 100+ entities within one's vision
PLAYER_N_OBS = 100
'''Number of distinct agent observations'''
MAX_HORIZON = 2**15 - 1 # this is arbitrary
'''Maximum number of steps the environment can run for'''
HORIZON = 1024
'''Number of steps before the environment resets'''
GAME_PACKS = None
'''List of game packs to load and sample: [(game class, sampling weight)]'''
CURRICULUM_FILE_PATH = None
'''Path to a curriculum task file containing a list of task specs for training'''
TASK_EMBED_DIM = 4096
'''Dimensionality of task embeddings'''
ALLOW_MULTI_TASKS_PER_AGENT = False
'''Whether to allow multiple tasks per agent'''
PROVIDE_ACTION_TARGETS = True
'''Provide action targets mask'''
PROVIDE_NOOP_ACTION_TARGET = True
'''Provide a no-op option for each action'''
PROVIDE_DEATH_FOG_OBS = False
'''Provide death fog observation'''
ALLOW_MOVE_INTO_OCCUPIED_TILE = True
'''Whether agents can move into tiles occupied by other agents/npcs
However, this does not apply to spawning'''
############################################################################
### System/debug Parameters
USE_CYTHON = True
'''Whether to use cython modules for performance'''
IMMORTAL = False
'''Debug parameter: prevents agents from dying except by void'''
############################################################################
### Player Parameters
PLAYER_BASE_HEALTH = 100
'''Initial agent health'''
PLAYER_VISION_RADIUS = 7
'''Number of tiles an agent can see in any direction'''
@property
def PLAYER_VISION_DIAMETER(self):
'''Size of the square tile crop visible to an agent'''
return 2*self.PLAYER_VISION_RADIUS + 1
PLAYER_HEALTH_INCREMENT = 0
'''The amount to increment health by 1 per tick for players, like npcs'''
DEATH_FOG_ONSET = None
'''How long before spawning death fog. None for no death fog'''
DEATH_FOG_SPEED = 1
'''Number of tiles per tick that the fog moves in'''
DEATH_FOG_FINAL_SIZE = 8
'''Number of tiles from the center that the fog stops'''
PLAYER_LOADER = spawn.SequentialLoader
'''Agent loader class specifying spawn sampling'''
############################################################################
### Team Parameters
TEAMS = None # Dict[Any, List[int]]
'''A dictionary of team assignments: key is team_id, value is a list of agent_ids'''
############################################################################
### Map Parameters
MAP_N = 1
'''Number of maps to generate'''
MAP_N_TILE = len(material.All.materials)
'''Number of distinct terrain tile types'''
@property
def MAP_N_OBS(self):
'''Number of distinct tile observations'''
return int(self.PLAYER_VISION_DIAMETER ** 2)
MAP_SIZE = None
'''Size of the whole map, including the center and borders'''
MAP_CENTER = None
'''Size of each map (number of tiles along each side), where agents can move around'''
@property
def MAP_BORDER(self):
'''Number of background, void border tiles surrounding each side of the map'''
return int((self.MAP_SIZE - self.MAP_CENTER) // 2)
MAP_GENERATOR = MapGenerator
'''Specifies a user map generator. Uses default generator if unspecified.'''
MAP_FORCE_GENERATION = True
'''Whether to regenerate and overwrite existing maps'''
MAP_RESET_FROM_FRACTAL = True
'''Whether to regenerate the map from the fractal source'''
MAP_GENERATE_PREVIEWS = False
'''Whether map generation should also save .png previews (slow + large file size)'''
MAP_PREVIEW_DOWNSCALE = 1
'''Downscaling factor for png previews'''
############################################################################
### Path Parameters
PATH_ROOT = os.path.dirname(nmmo.__file__)
'''Global repository directory'''
PATH_CWD = os.getcwd()
'''Working directory'''
PATH_RESOURCE = os.path.join(PATH_ROOT, 'resource')
'''Resource directory'''
PATH_TILE = os.path.join(PATH_RESOURCE, '{}.png')
'''Tile path -- format me with tile name'''
PATH_MAPS = None
'''Generated map directory'''
PATH_MAP_SUFFIX = 'map{}/map.npy'
'''Map file name'''
PATH_FRACTAL_SUFFIX = 'map{}/fractal.npy'
'''Fractal file name'''
############################################################################
### Game Systems (Static Mixins)
class Terrain:
'''Terrain Game System'''
TERRAIN_SYSTEM_ENABLED = True
'''Game system flag'''
TERRAIN_FLIP_SEED = False
'''Whether to negate the seed used for generation (useful for unique heldout maps)'''
TERRAIN_FREQUENCY = -3
'''Base noise frequency range (log2 space)'''
TERRAIN_FREQUENCY_OFFSET = 7
'''Noise frequency octave offset (log2 space)'''
TERRAIN_LOG_INTERPOLATE_MIN = -2
'''Minimum interpolation log-strength for noise frequencies'''
TERRAIN_LOG_INTERPOLATE_MAX = 0
'''Maximum interpolation log-strength for noise frequencies'''
TERRAIN_TILES_PER_OCTAVE = 8
'''Number of octaves sampled from log2 spaced TERRAIN_FREQUENCY range'''
TERRAIN_VOID = 0.0
'''Noise threshold for void generation'''
TERRAIN_WATER = 0.30
'''Noise threshold for water generation'''
TERRAIN_GRASS = 0.70
'''Noise threshold for grass'''
TERRAIN_FOILAGE = 0.85
'''Noise threshold for foilage (food tile)'''
TERRAIN_RESET_TO_GRASS = False
'''Whether to make all tiles grass.
Only works when MAP_RESET_FROM_FRACTAL is True'''
TERRAIN_DISABLE_STONE = False
'''Disable stone (obstacle) tiles'''
TERRAIN_SCATTER_EXTRA_RESOURCES = True
'''Whether to scatter extra food, water on the map.
Only works when MAP_RESET_FROM_FRACTAL is True'''
class Resource:
'''Resource Game System'''
RESOURCE_SYSTEM_ENABLED = True
'''Game system flag'''
RESOURCE_BASE = 100
'''Initial level and capacity for food and water'''
RESOURCE_DEPLETION_RATE = 5
'''Depletion rate for food and water'''
RESOURCE_STARVATION_RATE = 10
'''Damage per tick without food'''
RESOURCE_DEHYDRATION_RATE = 10
'''Damage per tick without water'''
RESOURCE_RESILIENT_POPULATION = 0
'''Training helper: proportion of population that is resilient to starvation and dehydration
(e.g. 0.1 means 10% of the population is resilient to starvation and dehydration)
This is to make some agents live longer during training to sample from "advanced" agents.'''
RESOURCE_DAMAGE_REDUCTION = 0.5
'''Training helper: damage reduction from starvation and dehydration for resilient agents'''
RESOURCE_FOILAGE_CAPACITY = 1
'''Maximum number of harvests before a foilage tile decays'''
RESOURCE_FOILAGE_RESPAWN = 0.025
'''Probability that a harvested foilage tile will regenerate each tick'''
RESOURCE_HARVEST_RESTORE_FRACTION = 1.0
'''Fraction of maximum capacity restored upon collecting a resource'''
RESOURCE_HEALTH_REGEN_THRESHOLD = 0.5
'''Fraction of maximum resource capacity required to regen health'''
RESOURCE_HEALTH_RESTORE_FRACTION = 0.1
'''Fraction of health restored per tick when above half food+water'''
# NOTE: Included self to be picklable (in torch.save) since lambdas are not picklable
def original_combat_damage_formula(self, offense, defense, multiplier, minimum_proportion):
# pylint: disable=unused-argument
return int(multiplier * (offense * (15 / (15 + defense))))
def alt_combat_damage_formula(self, offense, defense, multiplier, minimum_proportion):
# pylint: disable=unused-argument
return int(max(multiplier * offense - defense, offense * minimum_proportion))
class Combat:
'''Combat Game System'''
COMBAT_SYSTEM_ENABLED = True
'''Game system flag'''
COMBAT_SPAWN_IMMUNITY = 20
'''Agents older than this many ticks cannot attack agents younger than this many ticks'''
COMBAT_ALLOW_FLEXIBLE_STYLE = True
'''Whether to allow agents to attack with any style in a given turn'''
COMBAT_STATUS_DURATION = 3
'''Combat status lasts for this many ticks after the last combat event.
Combat events include both attacking and being attacked.'''
COMBAT_WEAKNESS_MULTIPLIER = 1.5
'''Multiplier for super-effective attacks'''
COMBAT_MINIMUM_DAMAGE_PROPORTION = 0.25
'''Minimum proportion of damage to inflict on a target'''
# NOTE: When using a custom function, include "self" as the first arg
COMBAT_DAMAGE_FORMULA = alt_combat_damage_formula
'''Damage formula'''
COMBAT_MELEE_DAMAGE = 10
'''Melee attack damage'''
COMBAT_MELEE_REACH = 3
'''Reach of attacks using the Melee skill'''
COMBAT_RANGE_DAMAGE = 10
'''Range attack damage'''
COMBAT_RANGE_REACH = 3
'''Reach of attacks using the Range skill'''
COMBAT_MAGE_DAMAGE = 10
'''Mage attack damage'''
COMBAT_MAGE_REACH = 3
'''Reach of attacks using the Mage skill'''
def default_exp_threshold(base_exp, max_level):
import math
additional_exp_per_level = [round(base_exp * math.sqrt(lvl))
for lvl in range(1, max_level+1)]
return [sum(additional_exp_per_level[:lvl]) for lvl in range(max_level)]
class Progression:
'''Progression Game System'''
PROGRESSION_SYSTEM_ENABLED = True
'''Game system flag'''
PROGRESSION_BASE_LEVEL = 1
'''Initial skill level'''
PROGRESSION_LEVEL_MAX = 10
'''Max skill level'''
PROGRESSION_EXP_THRESHOLD = default_exp_threshold(90, PROGRESSION_LEVEL_MAX)
'''A list of experience thresholds for each level'''
PROGRESSION_COMBAT_XP_SCALE = 6
'''Additional XP for each attack for skills Melee, Range, and Mage'''
PROGRESSION_AMMUNITION_XP_SCALE = 15
'''Additional XP for each harvest for Prospecting, Carving, and Alchemy'''
PROGRESSION_CONSUMABLE_XP_SCALE = 30
'''Multiplier XP for each harvest for Fishing and Herbalism'''
PROGRESSION_MELEE_BASE_DAMAGE = 10
'''Base Melee attack damage'''
PROGRESSION_MELEE_LEVEL_DAMAGE = 5
'''Bonus Melee attack damage per level'''
PROGRESSION_RANGE_BASE_DAMAGE = 10
'''Base Range attack damage'''
PROGRESSION_RANGE_LEVEL_DAMAGE = 5
'''Bonus Range attack damage per level'''
PROGRESSION_MAGE_BASE_DAMAGE = 10
'''Base Mage attack damage '''
PROGRESSION_MAGE_LEVEL_DAMAGE = 5
'''Bonus Mage attack damage per level'''
PROGRESSION_BASE_DEFENSE = 0
'''Base defense'''
PROGRESSION_LEVEL_DEFENSE = 5
'''Bonus defense per level'''
class NPC:
'''NPC Game System'''
NPC_SYSTEM_ENABLED = True
'''Game system flag'''
NPC_N = None
'''Maximum number of NPCs spawnable in the environment'''
NPC_DEFAULT_REFILL_DEAD_NPCS = True
'''Whether to refill dead NPCs'''
NPC_SPAWN_ATTEMPTS = 25
'''Number of NPC spawn attempts per tick'''
NPC_SPAWN_AGGRESSIVE = 0.80
'''Percentage distance threshold from spawn for aggressive NPCs'''
NPC_SPAWN_NEUTRAL = 0.50
'''Percentage distance threshold from spawn for neutral NPCs'''
NPC_SPAWN_PASSIVE = 0.00
'''Percentage distance threshold from spawn for passive NPCs'''
NPC_LEVEL_MIN = 1
'''Minimum NPC level'''
NPC_LEVEL_MAX = 10
'''Maximum NPC level'''
NPC_BASE_DEFENSE = 0
'''Base NPC defense'''
NPC_LEVEL_DEFENSE = 8
'''Bonus NPC defense per level'''
NPC_BASE_DAMAGE = 0
'''Base NPC damage'''
NPC_LEVEL_DAMAGE = 8
'''Bonus NPC damage per level'''
NPC_LEVEL_MULTIPLIER = 1.0
'''Multiplier for NPC level damage and defense, for easier difficulty tuning'''
NPC_ALLOW_ATTACK_OTHER_NPCS = False
'''Whether NPCs can attack other NPCs'''
class Item:
'''Inventory Game System'''
ITEM_SYSTEM_ENABLED = True
'''Game system flag'''
ITEM_N = 17
'''Number of unique base item classes'''
ITEM_INVENTORY_CAPACITY = 12
'''Number of inventory spaces'''
ITEM_ALLOW_GIFT = True
'''Whether agents can give gold/item to each other'''
@property
def INVENTORY_N_OBS(self):
'''Number of distinct item observations'''
return self.ITEM_INVENTORY_CAPACITY
class Equipment:
'''Equipment Game System'''
EQUIPMENT_SYSTEM_ENABLED = True
'''Game system flag'''
WEAPON_DROP_PROB = 0.025
'''Chance of getting a weapon while harvesting ammunition'''
EQUIPMENT_WEAPON_BASE_DAMAGE = 5
'''Base weapon damage'''
EQUIPMENT_WEAPON_LEVEL_DAMAGE = 5
'''Added weapon damage per level'''
EQUIPMENT_AMMUNITION_BASE_DAMAGE = 5
'''Base ammunition damage'''
EQUIPMENT_AMMUNITION_LEVEL_DAMAGE = 10
'''Added ammunition damage per level'''
EQUIPMENT_TOOL_BASE_DEFENSE = 15
'''Base tool defense'''
EQUIPMENT_TOOL_LEVEL_DEFENSE = 0
'''Added tool defense per level'''
EQUIPMENT_ARMOR_BASE_DEFENSE = 0
'''Base armor defense'''
EQUIPMENT_ARMOR_LEVEL_DEFENSE = 3
'''Base equipment defense'''
class Profession:
'''Profession Game System'''
PROFESSION_SYSTEM_ENABLED = True
'''Game system flag'''
PROFESSION_TREE_CAPACITY = 1
'''Maximum number of harvests before a tree tile decays'''
PROFESSION_TREE_RESPAWN = 0.105
'''Probability that a harvested tree tile will regenerate each tick'''
PROFESSION_ORE_CAPACITY = 1
'''Maximum number of harvests before an ore tile decays'''
PROFESSION_ORE_RESPAWN = 0.10
'''Probability that a harvested ore tile will regenerate each tick'''
PROFESSION_CRYSTAL_CAPACITY = 1
'''Maximum number of harvests before a crystal tile decays'''
PROFESSION_CRYSTAL_RESPAWN = 0.10
'''Probability that a harvested crystal tile will regenerate each tick'''
PROFESSION_HERB_CAPACITY = 1
'''Maximum number of harvests before an herb tile decays'''
PROFESSION_HERB_RESPAWN = 0.02
'''Probability that a harvested herb tile will regenerate each tick'''
PROFESSION_FISH_CAPACITY = 1
'''Maximum number of harvests before a fish tile decays'''
PROFESSION_FISH_RESPAWN = 0.02
'''Probability that a harvested fish tile will regenerate each tick'''
def PROFESSION_CONSUMABLE_RESTORE(self, level):
'''Amount of food/water restored by consuming a consumable item'''
return 50 + 5*level
class Exchange:
'''Exchange Game System'''
EXCHANGE_SYSTEM_ENABLED = True
'''Game system flag'''
EXCHANGE_BASE_GOLD = 1
'''Initial gold amount'''
EXCHANGE_LISTING_DURATION = 3
'''The number of ticks, during which the item is listed for sale'''
MARKET_N_OBS = 384 # this should be proportion to PLAYER_N
'''Number of distinct item observations'''
PRICE_N_OBS = 99 # make it different from PLAYER_N_OBS
'''Number of distinct price observations
This also determines the maximum price one can set for an item
'''
class Communication:
'''Exchange Game System'''
COMMUNICATION_SYSTEM_ENABLED = True
'''Game system flag'''
COMMUNICATION_N_OBS = 32
'''Number of players that share the same communication obs, i.e. the same team'''
COMMUNICATION_NUM_TOKENS = 127
'''Number of distinct COMM tokens'''
class AllGameSystems(
Terrain, Resource, Combat, NPC, Progression, Item,
Equipment, Profession, Exchange, Communication):
pass
############################################################################
### Config presets
class Small(Config):
'''A small config for debugging and experiments with an expensive outer loop'''
PATH_MAPS = 'maps/small'
PLAYER_N = 64
MAP_PREVIEW_DOWNSCALE = 4
MAP_SIZE = 64
MAP_CENTER = 32
TERRAIN_LOG_INTERPOLATE_MIN = 0
NPC_N = 32
NPC_LEVEL_MAX = 5
NPC_LEVEL_SPREAD = 1
PROGRESSION_SPAWN_CLUSTERS = 4
PROGRESSION_SPAWN_UNIFORMS = 16
HORIZON = 128
class Medium(Config):
'''A medium config suitable for most academic-scale research'''
PATH_MAPS = 'maps/medium'
PLAYER_N = 128
MAP_PREVIEW_DOWNSCALE = 16
MAP_SIZE = 160
MAP_CENTER = 128
NPC_N = 128
NPC_LEVEL_MAX = 10
NPC_LEVEL_SPREAD = 1
PROGRESSION_SPAWN_CLUSTERS = 64
PROGRESSION_SPAWN_UNIFORMS = 256
HORIZON = 1024
class Large(Config):
'''A large config suitable for large-scale research or fast models'''
PATH_MAPS = 'maps/large'
PLAYER_N = 1024
MAP_PREVIEW_DOWNSCALE = 64
MAP_SIZE = 1056
MAP_CENTER = 1024
NPC_N = 1024
NPC_LEVEL_MAX = 15
NPC_LEVEL_SPREAD = 3
PROGRESSION_SPAWN_CLUSTERS = 1024
PROGRESSION_SPAWN_UNIFORMS = 4096
HORIZON = 8192
class Default(Medium, AllGameSystems):
pass
================================================
FILE: nmmo/core/env.py
================================================
import os
import functools
from typing import Any, Dict, List, Callable
from collections import defaultdict
from copy import deepcopy
import gymnasium as gym
import dill
import numpy as np
from pettingzoo.utils.env import AgentID, ParallelEnv
import nmmo
from nmmo.core import realm
from nmmo.core import game_api
from nmmo.core.config import Default
from nmmo.core.observation import Observation
from nmmo.core.tile import Tile
from nmmo.entity.entity import Entity
from nmmo.systems.item import Item
from nmmo.task.game_state import GameStateGenerator
from nmmo.lib import seeding
class Env(ParallelEnv):
# Environment wrapper for Neural MMO using the Parallel PettingZoo API
#pylint: disable=no-value-for-parameter
def __init__(self,
config: Default = nmmo.config.Default(),
seed = None):
'''Initializes the Neural MMO environment.
Args:
config (Default, optional): Configuration object for the environment.
Defaults to nmmo.config.Default().
seed (int, optional): Random seed for the environment. Defaults to None.
'''
self._np_random = None
self._np_seed = None
self._reset_required = True
self.seed(seed)
super().__init__()
self.config = config
self.config.env_initialized = True
# Generate maps if they do not exist
config.MAP_GENERATOR(config).generate_all_maps(self._np_seed)
self.realm = realm.Realm(config, self._np_random)
self.tile_map = None
self.tile_obs_shape = None
self.possible_agents = self.config.POSSIBLE_AGENTS
self._alive_agents = None
self._current_agents = None
self._dead_this_tick = None
self.scripted_agents = set()
self.obs = {agent_id: Observation(self.config, agent_id)
for agent_id in self.possible_agents}
self._dummy_task_embedding = np.zeros(self.config.TASK_EMBED_DIM, dtype=np.float16)
self._dummy_obs = Observation(self.config, 0).empty_obs
self._comm_obs = {}
self._gamestate_generator = GameStateGenerator(self.realm, self.config)
self.game_state = None
self.tasks = None
self.agent_task_map = {}
# curriculum file path, if provided, should exist
self.curriculum_file_path = config.CURRICULUM_FILE_PATH
if self.curriculum_file_path is not None:
# try to open the file to check if it exists
with open(self.curriculum_file_path, 'rb') as f:
dill.load(f)
f.close()
self.game = None
# NOTE: The default game runs with the full provided config and unmodded realm.reset()
self.default_game = game_api.DefaultGame(self)
self.game_packs: List[game_api.Game] = None
if config.GAME_PACKS: # assume List[Tuple(class, weight)]
self.game_packs = [game_cls(self, weight) for game_cls, weight in config.GAME_PACKS]
@functools.cached_property
def _obs_space(self):
def box(rows, cols):
return gym.spaces.Box(
low=-2**15, high=2**15-1,
shape=(rows, cols),
dtype=np.int16)
def mask_box(length):
return gym.spaces.Box(low=0, high=1, shape=(length,), dtype=np.int8)
# NOTE: obs space-related config attributes must NOT be changed after init
num_tile_attributes = len(Tile.State.attr_name_to_col)
num_tile_attributes += 1 if self.config.original["PROVIDE_DEATH_FOG_OBS"] else 0
obs_space = {
"CurrentTick": gym.spaces.Discrete(self.config.MAX_HORIZON),
"AgentId": gym.spaces.Discrete(self.config.PLAYER_N+1),
"Tile": box(self.config.MAP_N_OBS, num_tile_attributes),
"Entity": box(self.config.PLAYER_N_OBS, Entity.State.num_attributes),
"Task": gym.spaces.Box(low=-2**15, high=2**15-1,
shape=(self.config.TASK_EMBED_DIM,),
dtype=np.float16),
}
# NOTE: cannot turn on a game system that was not enabled during env init
if self.config.original["ITEM_SYSTEM_ENABLED"]:
obs_space["Inventory"] = box(self.config.INVENTORY_N_OBS, Item.State.num_attributes)
if self.config.original["EXCHANGE_SYSTEM_ENABLED"]:
obs_space["Market"] = box(self.config.MARKET_N_OBS, Item.State.num_attributes)
if self.config.original["COMMUNICATION_SYSTEM_ENABLED"]:
# Comm obs cols: id, row, col, message
obs_space["Communication"] = box(self.config.COMMUNICATION_N_OBS, 4)
if self.config.original["PROVIDE_ACTION_TARGETS"]:
mask_spec = deepcopy(self._atn_space)
for atn_str in mask_spec:
for arg_str in mask_spec[atn_str]:
mask_spec[atn_str][arg_str] = mask_box(self._atn_space[atn_str][arg_str].n)
obs_space["ActionTargets"] = mask_spec
return gym.spaces.Dict(obs_space)
# pylint: disable=method-cache-max-size-none
@functools.lru_cache(maxsize=None)
def observation_space(self, agent: AgentID):
'''Neural MMO Observation Space
Args:
agent (AgentID): The ID of the agent.
Returns:
gym.spaces.Dict: The observation space for the agent.
'''
return self._obs_space
# NOTE: make sure this runs once during trainer init and does NOT change afterwards
@functools.cached_property
def _atn_space(self):
actions = {}
for atn in sorted(nmmo.Action.edges(self.config)):
if atn.enabled(self.config):
actions[atn.__name__] = {} # use the string key
for arg in sorted(atn.edges):
n = arg.N(self.config)
actions[atn.__name__][arg.__name__] = gym.spaces.Discrete(n)
actions[atn.__name__] = gym.spaces.Dict(actions[atn.__name__])
return gym.spaces.Dict(actions)
@functools.cached_property
def _str_atn_map(self):
'''Map action and argument names to their corresponding objects'''
str_map = {}
for atn in nmmo.Action.edges(self.config):
str_map[atn.__name__] = atn
for arg in atn.edges:
str_map[arg.__name__] = arg
return str_map
# pylint: disable=method-cache-max-size-none
@functools.lru_cache(maxsize=None)
def action_space(self, agent: AgentID):
'''Neural MMO Action Space
Args:
agent (AgentID): The ID of the agent.
Returns:
gym.spaces.Dict: The action space for the agent.
'''
return self._atn_space
############################################################################
# Core API
def reset(self, seed=None, options=None, # PettingZoo API args
map_id=None,
make_task_fn: Callable=None,
game: game_api.Game=None):
'''Resets the environment and returns the initial observations.
Args:
seed (int, optional): Random seed for the environment. Defaults to None.
options (dict, optional): Additional options for resetting the environment.
Defaults to None.
map_id (int, optional): The ID of the map to load. Defaults to None.
make_task_fn (callable, optional): Function to create tasks. Defaults to None.
game (Game, optional): The game to be played. Defaults to None.
Returns:
tuple: A tuple containing:
- obs (dict): Dictionary mapping agent IDs to their initial observations.
- info (dict): Dictionary containing additional information.
'''
# If options are provided, override the kwargs
if options is not None:
map_id = options.get('map_id', None) or map_id
make_task_fn = options.get('make_task_fn', None) or make_task_fn
game = options.get('game', None) or game
self.seed(seed)
map_dict = self._load_map_file(map_id)
# Choose and reset the game, realm, and tasks
if make_task_fn is not None:
# Use the provided tasks with the default game (full config, unmodded realm)
self.tasks = make_task_fn()
self.game = self.default_game
self.game.reset(self._np_random, map_dict, self.tasks) # also does realm.reset()
elif game is not None:
# Use the provided game, which comes with its own tasks
self.game = game
self.game.reset(self._np_random, map_dict)
self.tasks = self.game.tasks
elif self.curriculum_file_path is not None or self.game_packs is not None:
# Assume training -- pick a random game from the game packs
self.game = self.default_game
if self.game_packs:
weights = [game.sampling_weight for game in self.game_packs]
self.game = self._np_random.choice(self.game_packs, p=weights/np.sum(weights))
self.game.reset(self._np_random, map_dict)
# use the sampled tasks from self.game
self.tasks = self.game.tasks
else:
# Just reset the same game and tasks as before
self.game = self.default_game # full config, unmodded realm
self.game.reset(self._np_random, map_dict, self.tasks) # use existing tasks
if self.tasks is None:
self.tasks = self.game.tasks
else:
for task in self.tasks:
task.reset()
# Reset the agent vars
self._alive_agents = self.possible_agents
self._dead_this_tick = {}
self._map_task_to_agent()
self._current_agents = self.possible_agents # tracking alive + dead_this_tick
# Check scripted agents
self.scripted_agents.clear()
for eid, ent in self.realm.players.items():
if isinstance(ent.agent, nmmo.Scripted):
self.scripted_agents.add(eid)
ent.agent.set_rng(self._np_random)
# Tile map placeholder, to reduce redudunt obs computation
self.tile_map = Tile.Query.get_map(self.realm.datastore, self.config.MAP_SIZE)
if self.config.PROVIDE_DEATH_FOG_OBS:
fog_map = np.round(self.realm.fog_map)[:,:,np.newaxis].astype(np.int16)
self.tile_map = np.concatenate((self.tile_map, fog_map), axis=-1)
self.tile_obs_shape = (self.config.PLAYER_VISION_DIAMETER**2, self.tile_map.shape[-1])
# Reset the obs, game state generator
infos = {}
for agent_id in self.possible_agents:
# NOTE: the tasks for each agent is in self.agent_task_map, and task embeddings are
# available in each task instance, via task.embedding
# For now, each agent is assigned to a single task, so we just use the first task
# TODO: can the embeddings of multiple tasks be superposed while preserving the
# task-specific information? This needs research
task_embedding = self._dummy_task_embedding
if agent_id in self.agent_task_map:
task_embedding = self.agent_task_map[agent_id][0].embedding
infos[agent_id] = {"task": self.agent_task_map[agent_id][0].name}
self.obs[agent_id].reset(self.realm.map.habitable_tiles, task_embedding)
self._compute_observations()
self._gamestate_generator = GameStateGenerator(self.realm, self.config)
if self.game_state is not None:
self.game_state.clear_cache()
self.game_state = None
self._reset_required = False
return {a: o.to_gym() for a,o in self.obs.items()}, infos
def _load_map_file(self, map_id: int=None):
'''Loads a map file, which is a 2D numpy array'''
map_dict= {}
map_id = map_id or self._np_random.integers(self.config.MAP_N) + 1
map_file_path = os.path.join(self.config.PATH_CWD, self.config.PATH_MAPS,
self.config.PATH_MAP_SUFFIX.format(map_id))
map_dict["map"] = np.load(map_file_path)
if self.config.MAP_RESET_FROM_FRACTAL:
fractal_file_path = os.path.join(self.config.PATH_CWD, self.config.PATH_MAPS,
self.config.PATH_FRACTAL_SUFFIX.format(map_id))
map_dict["fractal"] = np.load(fractal_file_path).astype(float)
return map_dict
def _map_task_to_agent(self):
self.agent_task_map.clear()
for agent_id in self.agents:
self.realm.players[agent_id].my_task = None
for task in self.tasks:
if task.embedding is None:
task.set_embedding(self._dummy_task_embedding)
# map task to agents
for agent_id in task.assignee:
if agent_id in self.agent_task_map:
self.agent_task_map[agent_id].append(task)
else:
self.agent_task_map[agent_id] = [task]
# for now we only support one task per agent
if self.config.ALLOW_MULTI_TASKS_PER_AGENT is False:
for agent_id, agent_tasks in self.agent_task_map.items():
assert len(agent_tasks) == 1, "Only one task per agent is supported"
self.realm.players[agent_id].my_task = agent_tasks[0]
def step(self, actions: Dict[int, Dict[str, Dict[str, Any]]]):
'''Performs one step in the environment given the provided actions.
Args:
actions (dict): Dictionary mapping agent IDs to their actions.
Returns:
tuple: A tuple containing:
- obs (dict): Dictionary mapping agent IDs to their new observations.
- rewards (dict): Dictionary mapping agent IDs to their rewards.
- terminated (dict): Dictionary mapping agent IDs to whether they reached
a terminal state.
- truncated (dict): Dictionary mapping agent IDs to whether the episode was
truncated (e.g. reached maximum number of steps).
- infos (dict): Dictionary containing additional information.
'''
assert not self._reset_required, 'step() called before reset'
# Add in scripted agents' actions, if any
if self.scripted_agents:
actions = self._compute_scripted_agent_actions(actions)
# Drop invalid actions of BOTH neural and scripted agents
# we don't need _deserialize_scripted_actions() anymore
actions = self._validate_actions(actions)
# Execute actions
self._dead_this_tick, dead_npcs = self.realm.step(actions)
self._alive_agents = list(self.realm.players.keys())
self._current_agents = list(set(self._alive_agents + list(self._dead_this_tick.keys())))
terminated = {}
for agent_id in self._current_agents:
if agent_id in self._dead_this_tick:
# NOTE: Even though players can be resurrected, the time of death must be marked.
terminated[agent_id] = True
else:
terminated[agent_id] = False
if self.realm.tick >= self.config.HORIZON:
self._alive_agents = [] # pettingzoo requires agents to be empty
# Update the game stats, determine winners, etc.
# Also, resurrect dead agents and/or spawn new npcs if the game allows it
self.game.update(terminated, self._dead_this_tick, dead_npcs)
# Some games do additional player cull during update(), so process truncated here
truncated = {}
for agent_id in self._current_agents:
if self.realm.tick >= self.config.HORIZON:
truncated[agent_id] = agent_id in self.realm.players
else:
truncated[agent_id] = False
# Store the observations, since actions reference them
self._compute_observations()
gym_obs = {a: self.obs[a].to_gym() for a in self._current_agents}
rewards, infos = self._compute_rewards()
# NOTE: all obs, rewards, dones, infos have data for each agent in self.agents
return gym_obs, rewards, terminated, truncated, infos
@property
def dead_this_tick(self):
return self._dead_this_tick
def _validate_actions(self, actions: Dict[int, Dict[str, Dict[str, Any]]]):
'''Deserialize action arg values and validate actions
For now, it does a basic validation (e.g., value is not none).
'''
validated_actions = {}
for ent_id, atns in actions.items():
if ent_id not in self.realm.players:
#assert ent_id in self.realm.players, f'Entity {ent_id} not in realm'
continue # Entity not in the realm -- invalid actions
entity = self.realm.players[ent_id]
if not entity.alive:
#assert entity.alive, f'Entity {ent_id} is dead'
continue # Entity is dead -- invalid actions
validated_actions[ent_id] = {}
for atn_key, args in sorted(atns.items()):
action_valid = True
deserialized_action = {}
# If action/system is not enabled, it's not in self._str_atn_map
if isinstance(atn_key, str) and atn_key not in self._str_atn_map:
action_valid = False
continue
atn = self._str_atn_map[atn_key] if isinstance(atn_key, str) else atn_key
if not atn.enabled(self.config): # This can change from episode to episode
action_valid = False
continue
for arg_key, val in sorted(args.items()):
arg = self._str_atn_map[arg_key] if isinstance(arg_key, str) else arg_key
obj = arg.deserialize(self.realm, entity, val, self.obs[ent_id])
if obj is None:
action_valid = False
break
deserialized_action[arg] = obj
if action_valid:
validated_actions[ent_id][atn] = deserialized_action
return validated_actions
def _compute_scripted_agent_actions(self, actions: Dict[int, Dict[str, Dict[str, Any]]]):
'''Compute actions for scripted agents and add them into the action dict'''
dead_agents = set()
for agent_id in self.scripted_agents:
if agent_id in self.realm.players:
# override the provided scripted agents' actions
actions[agent_id] = self.realm.players[agent_id].agent(self.obs[agent_id])
else:
dead_agents.add(agent_id)
# remove the dead scripted agent from the list
self.scripted_agents -= dead_agents
return actions
def _compute_observations(self):
radius = self.config.PLAYER_VISION_RADIUS
market = Item.Query.for_sale(self.realm.datastore) \
if self.config.EXCHANGE_SYSTEM_ENABLED else None
self._update_comm_obs()
if self.config.PROVIDE_DEATH_FOG_OBS:
self.tile_map[:, :, -1] = np.round(self.realm.fog_map)
for agent_id in self._current_agents:
if agent_id not in self.realm.players:
self.obs[agent_id].set_agent_dead()
else:
r, c = self.realm.players.get(agent_id).pos
visible_entities = Entity.Query.window(self.realm.datastore, r, c, radius)
visible_tiles = self.tile_map[r-radius:r+radius+1,
c-radius:c+radius+1, :].reshape(self.tile_obs_shape)
inventory = Item.Query.owned_by(self.realm.datastore, agent_id) \
if self.config.ITEM_SYSTEM_ENABLED else None
comm_obs = self._comm_obs[agent_id] \
if self.config.COMMUNICATION_SYSTEM_ENABLED else None
self.obs[agent_id].update(self.realm.tick, visible_tiles, visible_entities,
inventory=inventory, market=market, comm=comm_obs)
def _update_comm_obs(self):
if not self.config.COMMUNICATION_SYSTEM_ENABLED:
return
comm_obs = Entity.Query.comm_obs(self.realm.datastore)
agent_ids = comm_obs[:, Entity.State.attr_name_to_col['id']]
self._comm_obs.clear()
for agent_id in self.realm.players:
if agent_id not in self._comm_obs:
my_team = [agent_id] if agent_id not in self.agent_task_map \
else self.agent_task_map[agent_id][0].assignee # NOTE: first task only
team_obs = [comm_obs[agent_ids == eid] for eid in my_team]
if len(team_obs) == 1:
team_obs = team_obs[0]
else:
team_obs = np.concatenate(team_obs, axis=0)
for eid in my_team:
self._comm_obs[eid] = team_obs
def _compute_rewards(self):
# Initialization
agents = set(self._current_agents)
infos = {agent_id: {'task': {}} for agent_id in agents}
rewards = defaultdict(int)
# Clean up unnecessary game state, which cause memory leaks
if self.game_state is not None:
self.game_state.clear_cache()
self.game_state = None
# Compute Rewards and infos
self.game_state = self._gamestate_generator.generate(self.realm, self.obs)
for task in self.tasks:
if agents.intersection(task.assignee): # evaluate only if the agents are current
task_rewards, task_infos = task.compute_rewards(self.game_state)
for agent_id, reward in task_rewards.items():
if agent_id in agents:
rewards[agent_id] = rewards.get(agent_id,0) + reward
infos[agent_id]['task'][task.name] = task_infos[agent_id] # include progress, etc.
else:
task.close() # To prevent memory leak
# Reward for frozen agents (recon, resurrected, frozen) is 0 because they cannot act
for agent_id, agent in self.realm.players.items():
if agent.status.frozen:
rewards[agent_id] = 0
# Reward for dead agents is defined by the game
# NOTE: Resurrected agents are frozen and in the realm.players, so run through
# self._dead_this_tick to give out the dead reward
if self.game.assign_dead_reward:
for agent_id in self._dead_this_tick:
rewards[agent_id] = -1
return rewards, infos
############################################################################
# PettingZoo API
############################################################################
def render(self, mode='human'):
'''For conformity with the PettingZoo API only; rendering is external'''
@property
def agents(self) -> List[AgentID]:
'''For conformity with the PettingZoo API; retuning only the alive agents'''
return self._alive_agents
def close(self):
'''For conformity with the PettingZoo API only; rendering is external'''
def seed(self, seed=None):
'''Reseeds the environment. reset() must be called after seed(), and before step().
- self._np_seed is None: seed() has not been called, e.g. __init__() -> new RNG
- self._np_seed is set, and seed is not None: seed() or reset() with seed -> new RNG
If self._np_seed is set, but seed is None
probably called from reset() without seed, so don't change the RNG
'''
if self._np_seed is None or seed is not None:
self._np_random, self._np_seed = seeding.np_random(seed)
self._reset_required = True
def state(self) -> np.ndarray:
raise NotImplementedError
metadata = {'render.modes': ['human'], 'name': 'neural-mmo'}
================================================
FILE: nmmo/core/game_api.py
================================================
# pylint: disable=no-member,bare-except
from abc import ABC, abstractmethod
from typing import Dict
from collections import deque
import dill
import numpy as np
from nmmo.task import task_api, task_spec, base_predicates
from nmmo.lib import team_helper, utils
GAME_MODE = ["agent_training", "team_training", "team_battle"]
class Game(ABC):
game_mode = None
def __init__(self, env, sampling_weight=None):
self.config = env.config
self.realm = env.realm
self._np_random = env._np_random
self.sampling_weight = sampling_weight or 1.0
self.tasks = None
self.assign_dead_reward = True
self._next_tasks = None
self._agent_stats = {}
self._winners = None
self._game_done = False
self.history: deque[Dict] = deque(maxlen=100)
assert self.is_compatible(), "Game is not compatible with the config"
@abstractmethod
def is_compatible(self):
"""Check if the game is compatible with the config (e.g., required systems)"""
raise NotImplementedError
@property
def name(self):
return self.__class__.__name__
@property
def winners(self):
return self._winners
@property
def winning_score(self):
if self._winners:
# CHECK ME: should we return the winners" tasks" reward multiplier?
return 1.0 # default score for task completion
return 0.0
def reset(self, np_random, map_dict, tasks=None):
self._np_random = np_random
self._set_config()
self._set_realm(map_dict)
if tasks:
# tasks comes from env.reset()
self.tasks = tasks
elif self._next_tasks:
# env.reset() cannot take both game and tasks
# so set next_tasks in the game first
self.tasks = self._next_tasks
self._next_tasks = None
else:
self.tasks = self._define_tasks()
self._post_setup()
self._reset_stats()
def _set_config(self): # pylint: disable=unused-argument
"""Set config for the episode. Can customize config using config.set_for_episode()"""
self.config.reset()
def _set_realm(self, map_dict):
"""Set up the realm for the episode. Can customize map and spawn"""
self.realm.reset(self._np_random, map_dict, custom_spawn=False)
def _post_setup(self):
"""Post-setup processes, e.g., attach team tags, etc."""
def _reset_stats(self):
"""Reset stats for the episode"""
self._agent_stats.clear()
self._winners = None
self._game_done = False
# result = False means the game ended without a winner
self.history.append({"result": False, "winners": None, "winning_score": None})
@abstractmethod
def _define_tasks(self):
"""Define tasks for the episode."""
# NOTE: Task embeddings should be provided somehow, e.g., from curriculum file.
# Otherwise, policies cannot be task-conditioned.
raise NotImplementedError
def set_next_tasks(self, tasks):
"""Set the next task to be completed"""
self._next_tasks = tasks
def update(self, terminated, dead_players, dead_npcs):
"""Process dead players/npcs, update the game stats, winners, etc."""
self._process_dead_players(terminated, dead_players)
self._process_dead_npcs(dead_npcs)
self._winners = self._check_winners(terminated)
if self._winners and not self._game_done:
self._game_done = self.history[-1]["result"] = True
self.history[-1]["winners"] = self._winners
self.history[-1]["winning_score"] = self.winning_score
self.history[-1]["winning_tick"] = self.realm.tick
self.history[-1].update(self.get_episode_stats())
def _process_dead_players(self, terminated, dead_players):
for agent_id in terminated:
if terminated[agent_id]:
agent = dead_players[agent_id] if agent_id in dead_players\
else self.realm.players[agent_id]
self._agent_stats[agent_id] = {"time_alive": self.realm.tick,
"progress_to_center": agent.history.exploration}
def _process_dead_npcs(self, dead_npcs):
if self.config.NPC_SYSTEM_ENABLED and self.config.NPC_DEFAULT_REFILL_DEAD_NPCS:
for npc in dead_npcs.values():
if npc.spawn_danger:
self.realm.npcs.spawn_dangers.append(npc.spawn_danger)
# refill npcs to target config.NPC_N, within config.NPC_SPAWN_ATTEMPTS
self.realm.npcs.default_spawn()
def _check_winners(self, terminated):
# Determine winners for the default task
if self.realm.num_players == 1: # only one survivor
return list(self.realm.players.keys())
if all(terminated.values()):
# declare all winners when they died at the same time
return list(terminated.keys())
if self.realm.tick >= self.config.HORIZON:
# declare all survivors as winners when the time is up
return [agent_id for agent_id, done in terminated.items() if not done]
return None
@property
def is_over(self):
return self.winners is not None or self.realm.num_players == 0 or \
self.realm.tick >= self.config.HORIZON
def get_episode_stats(self):
"""A helper function for trainers"""
total_agent_steps = 0
progress_to_center = 0
max_progress = self.config.PLAYER_N * self.config.MAP_SIZE // 2
for stat in self._agent_stats.values():
total_agent_steps += stat["time_alive"]
progress_to_center += stat["progress_to_center"]
return {
"total_agent_steps": total_agent_steps,
"norm_progress_to_center": float(progress_to_center) / max_progress
}
############################
# Helper functions for Game
def _who_completed_task(self):
# Return all assignees who completed their tasks
winners = []
for task in self.tasks:
if task.completed:
winners += task.assignee
return winners or None
class DefaultGame(Game):
"""The default NMMO game"""
game_mode = "agent_training"
def is_compatible(self):
return True
def _define_tasks(self):
return task_api.nmmo_default_task(self.config.POSSIBLE_AGENTS)
class AgentTraining(Game):
"""Game setting for agent training tasks"""
game_mode = "agent_training"
@property
def winning_score(self):
return 0.0
def is_compatible(self):
try:
# Check is the curriculum file exists and opens
with open(self.config.CURRICULUM_FILE_PATH, "rb") as f:
dill.load(f) # a list of TaskSpec
except:
return False
return True
def _define_tasks(self):
with open(self.config.CURRICULUM_FILE_PATH, "rb") as f:
# curriculum file may have been changed, so read the file when sampling
curriculum = dill.load(f) # a list of TaskSpec
cand_specs = [spec for spec in curriculum if spec.reward_to == "agent"]
assert len(cand_specs) > 0, "No agent task is defined in the curriculum file"
sampling_weights = [spec.sampling_weight for spec in cand_specs]
sampled_spec = self._np_random.choice(cand_specs, size=self.config.PLAYER_N,
p=sampling_weights/np.sum(sampling_weights))
return task_spec.make_task_from_spec(self.config.POSSIBLE_AGENTS, sampled_spec)
class TeamGameTemplate(Game):
"""A helper class with common utils for team games"""
assign_dead_reward = False # Do NOT always assign -1 to dead agents
def is_compatible(self):
try:
assert self.config.TEAMS is not None, "Team game requires TEAMS to be defined"
num_agents = sum(len(v) for v in self.config.TEAMS.values())
assert self.config.PLAYER_N == num_agents,\
"PLAYER_N must match the number of agents in TEAMS"
# Check is the curriculum file exists and opens
with open(self.config.CURRICULUM_FILE_PATH, "rb") as f:
dill.load(f) # a list of TaskSpec
except:
return False
return True
def _set_realm(self, map_dict):
self.realm.reset(self._np_random, map_dict, custom_spawn=True)
# Custom spawning
team_loader = team_helper.TeamLoader(self.config, self._np_random)
self.realm.players.spawn(team_loader)
self.realm.npcs.default_spawn()
def _post_setup(self):
self._attach_team_tag()
@property
def teams(self):
return self.config.TEAMS
def _attach_team_tag(self):
# setup team names
for team_id, members in self.teams.items():
if isinstance(team_id, int):
team_id = f"Team{team_id:02d}"
for idx, agent_id in enumerate(members):
self.realm.players[agent_id].name = f"{team_id}_{agent_id}"
if idx == 0:
self.realm.players[agent_id].name = f"{team_id}_leader"
def _get_cand_team_tasks(self, num_tasks, tags=None):
# NOTE: use different file to store different set of tasks?
with open(self.config.CURRICULUM_FILE_PATH, "rb") as f:
curriculum = dill.load(f) # a list of TaskSpec
cand_specs = [spec for spec in curriculum if spec.reward_to == "team"]
if tags:
cand_specs = [spec for spec in cand_specs if tags in spec.tags]
assert len(cand_specs) > 0, "No team task is defined in the curriculum file"
sampling_weights = [spec.sampling_weight for spec in cand_specs]
sampled_spec = self._np_random.choice(cand_specs, size=num_tasks,
p=sampling_weights/np.sum(sampling_weights))
return sampled_spec
class TeamTraining(TeamGameTemplate):
"""Game setting for team training tasks"""
game_mode = "team_training"
def _define_tasks(self):
sampled_spec = self._get_cand_team_tasks(len(self.config.TEAMS))
return task_spec.make_task_from_spec(self.config.TEAMS, sampled_spec)
def team_survival_task(num_tick, embedding=None):
return task_spec.TaskSpec(
eval_fn=base_predicates.TickGE,
eval_fn_kwargs={"num_tick": num_tick},
reward_to="team",
embedding=embedding)
class TeamBattle(TeamGameTemplate):
"""Game setting for team battle"""
game_mode = "team_battle"
def __init__(self, env, sampling_weight=None):
super().__init__(env, sampling_weight)
self.task_embedding = utils.get_hash_embedding(base_predicates.TickGE,
self.config.TASK_EMBED_DIM)
def is_compatible(self):
assert self.config.are_systems_enabled(["COMBAT"]), "Combat system must be enabled"
assert self.config.TEAMS is not None, "Team battle mode requires TEAMS to be defined"
num_agents = sum(len(v) for v in self.config.TEAMS.values())
assert self.config.PLAYER_N == num_agents,\
"PLAYER_N must match the number of agents in TEAMS"
return True
def _define_tasks(self):
# NOTE: Teams can win by eliminating all other teams,
# or fully cooperating to survive for the entire episode
survive_task = team_survival_task(self.config.HORIZON, self.task_embedding)
return task_spec.make_task_from_spec(self.config.TEAMS,
[survive_task] * len(self.config.TEAMS))
def _check_winners(self, terminated):
# A team is won, when their task is completed first or only one team remains
current_teams = self._check_remaining_teams()
if len(current_teams) == 1:
winner_team = list(current_teams.keys())[0]
return self.config.TEAMS[winner_team]
# Return all assignees who completed their tasks
# Assuming the episode gets ended externally
return self._who_completed_task()
def _check_remaining_teams(self):
current_teams = {}
for team_id, team in self.config.TEAMS.items():
alive_members = [agent_id for agent_id in team if agent_id in self.realm.players]
if len(alive_members) > 0:
current_teams[team_id] = alive_members
return current_teams
class ProtectTheKing(TeamBattle):
def __init__(self, env, sampling_weight=None):
super().__init__(env, sampling_weight)
self.team_helper = team_helper.TeamHelper(self.config.TEAMS)
self.task_embedding = utils.get_hash_embedding(base_predicates.ProtectLeader,
self.config.TASK_EMBED_DIM)
def _define_tasks(self):
protect_task = task_spec.TaskSpec(
eval_fn=base_predicates.ProtectLeader,
eval_fn_kwargs={
"target_protect": "my_team_leader",
"target_destroy": "all_foe_leaders",
},
reward_to="team"
)
return task_spec.make_task_from_spec(self.config.TEAMS,
[protect_task] * len(self.config.TEAMS))
def update(self, terminated, dead_players, dead_npcs):
# If a team's leader is dead, the whole team is dead
for team_id, members in self.config.TEAMS.items():
if self.team_helper.get_target_agent(team_id, "my_team_leader") in dead_players:
for agent_id in members:
if agent_id in self.realm.players:
self.realm.players[agent_id].health.update(0)
# Addition dead players cull
for agent in [agent for agent in self.realm.players.values() if not agent.alive]:
agent_id = agent.ent_id
self.realm.players.dead_this_tick[agent_id] = agent
self.realm.players.cull_entity(agent)
agent.datastore_record.delete()
terminated[agent_id] = True
super().update(terminated, dead_players, dead_npcs)
================================================
FILE: nmmo/core/map.py
================================================
from typing import List, Tuple
import numpy as np
from ordered_set import OrderedSet
from nmmo.core.tile import Tile
from nmmo.lib import material, utils
from nmmo.core.terrain import (
fractal_to_material,
process_map_border,
spawn_profession_resources,
scatter_extra_resources,
)
class Map:
'''Map object representing a list of tiles
Also tracks a sparse list of tile updates
'''
def __init__(self, config, realm, np_random):
self.config = config
self._repr = None
self.realm = realm
self.update_list = None
self.pathfinding_cache = {} # Avoid recalculating A*, paths don't move
sz = config.MAP_SIZE
self.tiles = np.zeros((sz,sz), dtype=object)
self.habitable_tiles = np.zeros((sz,sz), dtype=np.int8)
for r in range(sz):
for c in range(sz):
self.tiles[r, c] = Tile(realm, r, c, np_random)
# the map center, and the centers in each quadrant are important targets
self.dist_border_center = None
self.center_coord = None
self.quad_centers = None
self.seize_targets: List[Tuple] = None # a list of (r, c) coords
# used to place border
self.l1 = utils.l1_map(sz)
@property
def packet(self):
'''Packet of degenerate resource states'''
missing_resources = []
for e in self.update_list:
missing_resources.append(e.pos)
return missing_resources
@property
def repr(self):
'''Flat matrix of tile material indices'''
if not self._repr:
self._repr = [[t.material.index for t in row] for row in self.tiles]
return self._repr
def reset(self, map_dict, np_random, seize_targets=None):
'''Reuse the current tile objects to load a new map'''
config = self.config
assert map_dict["map"].shape == (config.MAP_SIZE,config.MAP_SIZE),\
"Map shape is inconsistent with config.MAP_SIZE"
# NOTE: MAP_CENTER and MAP_BORDER can change from episode to episode
self.center_coord = (config.MAP_SIZE//2, config.MAP_SIZE//2)
self.dist_border_center = config.MAP_CENTER // 2
half_dist = self.dist_border_center // 2
self.quad_centers = {
"first": (self.center_coord[0] + half_dist, self.center_coord[1] + half_dist),
"second": (self.center_coord[0] - half_dist, self.center_coord[1] + half_dist),
"third": (self.center_coord[0] - half_dist, self.center_coord[1] - half_dist),
"fourth": (self.center_coord[0] + half_dist, self.center_coord[1] - half_dist),
}
assert config.MAP_BORDER > config.PLAYER_VISION_RADIUS,\
"MAP_BORDER must be greater than PLAYER_VISION_RADIUS"
self._repr = None
self.update_list = OrderedSet() # critical for determinism
self.seize_targets = []
if seize_targets:
assert isinstance(seize_targets, list), "seize_targets must be a list of reserved words"
for target in seize_targets:
# pylint: disable=consider-iterating-dictionary
assert target in list(self.quad_centers.keys()) + ["center"], "Invalid seize target"
self.seize_targets.append(self.center_coord if target == "center"
else self.quad_centers[target])
# process map_np_array according to config
matl_map = self._process_map(map_dict, np_random)
if "mark_center" in map_dict and map_dict["mark_center"]:
self._mark_tile(matl_map, *self.center_coord)
for r, c in self.seize_targets:
self._mark_tile(matl_map, r, c)
# reset tiles with new materials
materials = {mat.index: mat for mat in material.All}
for r, row in enumerate(matl_map):
for c, idx in enumerate(row):
mat = materials[idx]
tile = self.tiles[r, c]
tile.reset(mat, config, np_random)
self.habitable_tiles[r, c] = tile.habitable
def _process_map(self, map_dict, np_random):
map_np_array = map_dict["map"]
if not self.config.TERRAIN_SYSTEM_ENABLED:
map_np_array[:] = material.Grass.index
else:
if self.config.MAP_RESET_FROM_FRACTAL:
map_tiles = fractal_to_material(self.config, map_dict["fractal"],
self.config.TERRAIN_RESET_TO_GRASS)
# Place materials here, before converting map_tiles into an int array
if self.config.PROFESSION_SYSTEM_ENABLED:
spawn_profession_resources(self.config, map_tiles, np_random)
if self.config.TERRAIN_SCATTER_EXTRA_RESOURCES:
scatter_extra_resources(self.config, map_tiles, np_random)
map_np_array = map_tiles.astype(int)
# Disable materials here
if self.config.TERRAIN_DISABLE_STONE:
map_np_array[map_np_array == material.Stone.index] = material.Grass.index
# Make the edge tiles habitable, and place the void tiles outside the border
map_np_array = process_map_border(self.config, map_np_array, self.l1)
return map_np_array
@staticmethod
def _mark_tile(map_np_array, row, col, dist=2):
map_np_array[row-dist:row+dist+1,col-dist:col+dist+1] = material.Grass.index
map_np_array[row,col] = material.Herb.index
def step(self):
'''Evaluate updatable tiles'''
for tile in self.update_list.copy():
if not tile.depleted:
self.update_list.remove(tile)
tile.step()
if self.seize_targets:
for r, c in self.seize_targets:
self.tiles[r, c].update_seize()
def harvest(self, r, c, deplete=True):
'''Called by actions that harvest a resource tile'''
if deplete:
self.update_list.add(self.tiles[r, c])
return self.tiles[r, c].harvest(deplete)
def is_valid_pos(self, row, col):
'''Check if a position is valid'''
return 0 <= row < self.config.MAP_SIZE and 0 <= col < self.config.MAP_SIZE
def make_spawnable(self, row, col, radius=2):
'''Make the area centered around row, col spawnable'''
assert self._repr is None, "Cannot make spawnable after map is generated"
assert radius > 0, "Radius must be positive"
assert self.config.MAP_BORDER < row-radius and self.config.MAP_BORDER < col-radius \
and row+radius < self.config.MAP_SIZE-self.config.MAP_BORDER \
and col+radius < self.config.MAP_SIZE-self.config.MAP_BORDER,\
"Cannot make spawnable near the border"
for r in range(row-radius, row+radius+1):
for c in range(col-radius, col+radius+1):
tile = self.tiles[r, c]
# pylint: disable=protected-access
tile.reset(material.Grass, self.config, self.realm._np_random)
self.habitable_tiles[r, c] = tile.habitable # must be true
@property
def seize_status(self):
if self.seize_targets is None:
return {}
return {
(r, c): self.tiles[r, c].seize_history[-1]
for r, c in self.seize_targets
if self.tiles[r, c].seize_history
}
================================================
FILE: nmmo/core/observation.py
================================================
# pylint: disable=no-member,c-extension-no-member
from functools import lru_cache
import numpy as np
from nmmo.core.tile import TileState
from nmmo.entity.entity import EntityState
from nmmo.systems.item import ItemState
import nmmo.systems.item as item_system
from nmmo.core import action
from nmmo.lib import material
import nmmo.lib.cython_helper as chp
ROW_DELTA = np.array([-1, 1, 0, 0], dtype=np.int64)
COL_DELTA = np.array([0, 0, 1, -1], dtype=np.int64)
EMPTY_TILE = TileState.parse_array(
np.array([0, 0, material.Void.index], dtype=np.int16))
class BasicObs:
def __init__(self, id_col, obs_dim):
self.values = None
self.ids = None
self.id_col = id_col
self.obs_dim = obs_dim
def reset(self):
self.values = None
self.ids = None
def update(self, values):
self.values = values[:self.obs_dim]
self.ids = values[:, self.id_col]
@property
def len(self):
return self.ids.shape[0]
def id(self, i):
return self.ids[i] if i < self.len else None
def index(self, val):
return np.nonzero(self.ids == val)[0][0] if val in self.ids else None
class InventoryObs(BasicObs):
def __init__(self, id_col, obs_dim):
super().__init__(id_col, obs_dim)
self.inv_type = None
self.inv_level = None
def update(self, values):
super().update(values)
self.inv_type = self.values[:,ItemState.State.attr_name_to_col["type_id"]]
self.inv_level = self.values[:,ItemState.State.attr_name_to_col["level"]]
def sig(self, item: item_system.Item, level: int):
idx = np.nonzero((self.inv_type == item.ITEM_TYPE_ID) & (self.inv_level == level))[0]
return idx[0] if len(idx) else None
class GymObs:
keys_to_clear = ["Tile", "Entity", "Inventory", "Market", "Communication"]
def __init__(self, config, agent_id):
self.config = config
self.agent_id = agent_id
self.values = self._make_empty_obs()
def reset(self, task_embedding=None):
self.clear()
self.values["Task"][:] = 0 if task_embedding is None else task_embedding
def clear(self, tick=None):
self.values["CurrentTick"] = tick or 0
for key in self.keys_to_clear:
if key in self.values:
if key == "Inventory" and not self.config.ITEM_SYSTEM_ENABLED:
continue
if key == "Market" and not self.config.EXCHANGE_SYSTEM_ENABLED:
continue
if key == "Communication" and not self.config.COMMUNICATION_SYSTEM_ENABLED:
continue
self.values[key][:] = 0
def _make_empty_obs(self):
num_tile_attributes = TileState.State.num_attributes
num_tile_attributes += 1 if self.config.original["PROVIDE_DEATH_FOG_OBS"] else 0
gym_obs = {
"CurrentTick": 0,
"AgentId": self.agent_id,
"Task": np.zeros(self.config.TASK_EMBED_DIM, dtype=np.float16),
"Tile": np.zeros((self.config.MAP_N_OBS, num_tile_attributes), dtype=np.int16),
"Entity": np.zeros((self.config.PLAYER_N_OBS,
EntityState.State.num_attributes), dtype=np.int16)}
if self.config.original["ITEM_SYSTEM_ENABLED"]:
gym_obs["Inventory"] = np.zeros((self.config.INVENTORY_N_OBS,
ItemState.State.num_attributes), dtype=np.int16)
if self.config.original["EXCHANGE_SYSTEM_ENABLED"]:
gym_obs["Market"] = np.zeros((self.config.MARKET_N_OBS,
ItemState.State.num_attributes), dtype=np.int16)
if self.config.original["COMMUNICATION_SYSTEM_ENABLED"]:
gym_obs["Communication"] = np.zeros((self.config.COMMUNICATION_N_OBS,
len(EntityState.State.comm_attr_map)),
dtype=np.int16)
return gym_obs
def set_arr_values(self, key, values):
obs_shape = self.values[key].shape
self.values[key][:values.shape[0], :] = values[:, :obs_shape[1]]
def export(self):
return self.values.copy() # shallow copy
class ActionTargets:
no_op_keys = ["Direction", "Target", "InventoryItem", "MarketItem"]
all_ones = ["Style", "Price", "Token"]
def __init__(self, config):
self.config = config
if not self.config.original["PROVIDE_ACTION_TARGETS"]:
return
self._no_op = 1 if config.original["PROVIDE_NOOP_ACTION_TARGET"] else 0
self.values = self._make_empty_targets()
self.keys_to_clear = None
self.clear(reset=True) # to set the no-op option to 1, if needed
def _get_keys_to_clear(self):
keys = []
if self.config.COMBAT_SYSTEM_ENABLED:
keys.append("Attack")
if self.config.ITEM_SYSTEM_ENABLED:
keys.extend(["Use", "Give", "Destroy"])
if self.config.EXCHANGE_SYSTEM_ENABLED:
keys.extend(["Sell", "Buy", "GiveGold"])
if self.config.COMMUNICATION_SYSTEM_ENABLED:
keys.append("Comm")
return keys
def reset(self):
if not self.config.original["PROVIDE_ACTION_TARGETS"]:
return
self.keys_to_clear = self._get_keys_to_clear()
self.clear(reset=True)
def clear(self, reset=False):
if not self.config.original["PROVIDE_ACTION_TARGETS"]:
return
for key, mask in self.values.items():
if reset is True or key in self.keys_to_clear:
for sub_key in mask:
mask[sub_key][:] = 1 if sub_key in self.all_ones else 0
if self._no_op > 0 and sub_key in self.no_op_keys:
mask[sub_key][-1] = 1 # set the no-op option to 1
def _make_empty_targets(self):
masks = {}
masks["Move"] = {"Direction": np.zeros(len(action.Direction.edges), dtype=np.int8)}
if self.config.original["COMBAT_SYSTEM_ENABLED"]:
masks["Attack"] = {
"Style": np.ones(len(action.Style.edges), dtype=np.int8),
"Target": np.zeros(self.config.PLAYER_N_OBS + self._no_op, dtype=np.int8)}
if self.config.original["ITEM_SYSTEM_ENABLED"]:
masks["Use"] = {
"InventoryItem": np.zeros(self.config.INVENTORY_N_OBS + self._no_op, dtype=np.int8)}
masks["Give"] = {
"InventoryItem": np.zeros(self.config.INVENTORY_N_OBS + self._no_op, dtype=np.int8),
"Target": np.zeros(self.config.PLAYER_N_OBS + self._no_op, dtype=np.int8)}
masks["Destroy"] = {
"InventoryItem": np.zeros(self.config.INVENTORY_N_OBS + self._no_op, dtype=np.int8)}
if self.config.original["EXCHANGE_SYSTEM_ENABLED"]:
masks["Sell"] = {
"InventoryItem": np.zeros(self.config.INVENTORY_N_OBS + self._no_op, dtype=np.int8),
"Price": np.ones(self.config.PRICE_N_OBS, dtype=np.int8)}
masks["Buy"] = {
"MarketItem": np.zeros(self.config.MARKET_N_OBS + self._no_op, dtype=np.int8)}
masks["GiveGold"] = {
"Price": np.ones(self.config.PRICE_N_OBS, dtype=np.int8),
"Target": np.zeros(self.config.PLAYER_N_OBS + self._no_op, dtype=np.int8)}
if self.config.original["COMMUNICATION_SYSTEM_ENABLED"]:
masks["Comm"] = {"Token": np.ones(self.config.COMMUNICATION_NUM_TOKENS, dtype=np.int8)}
return masks
class Observation:
def __init__(self, config, agent_id: int) -> None:
self.config = config
self.agent_id = agent_id
self.agent = None
self.current_tick = None
self._is_agent_dead = None
self.habitable_tiles = None
self.agent_in_combat = None
self.gym_obs = GymObs(config, agent_id)
self.empty_obs = GymObs(config, agent_id).export()
self.action_targets = ActionTargets(config)
if self.config.original["PROVIDE_ACTION_TARGETS"]:
self.empty_obs["ActionTargets"] = ActionTargets(config).values
self.vision_radius = self.config.PLAYER_VISION_RADIUS
self.vision_diameter = self.config.PLAYER_VISION_DIAMETER
self._noop_action = 1 if config.original["PROVIDE_NOOP_ACTION_TARGET"] else 0
self.tiles = None
self.entities = BasicObs(EntityState.State.attr_name_to_col["id"],
config.PLAYER_N_OBS)
self.inventory = InventoryObs(ItemState.State.attr_name_to_col["id"],
config.INVENTORY_N_OBS) \
if config.original["ITEM_SYSTEM_ENABLED"] else None
self.market = BasicObs(ItemState.State.attr_name_to_col["id"],
config.MARKET_N_OBS) \
if config.original["EXCHANGE_SYSTEM_ENABLED"] else None
self.comm = BasicObs(EntityState.State.attr_name_to_col["id"],
config.COMMUNICATION_N_OBS) \
if config.original["COMMUNICATION_SYSTEM_ENABLED"] else None
def reset(self, habitable_tiles, task_embedding=None):
self.gym_obs.reset(task_embedding)
self.action_targets.reset()
self.habitable_tiles = habitable_tiles
self._is_agent_dead = False
self.agent_in_combat = None
self.current_tick = 0
self.tiles = None
self.entities.reset()
if self.config.ITEM_SYSTEM_ENABLED:
self.inventory.reset()
if self.config.EXCHANGE_SYSTEM_ENABLED:
self.market.reset()
if self.config.COMMUNICATION_SYSTEM_ENABLED:
self.comm.reset()
return self
@property
def return_dummy_obs(self):
return self._is_agent_dead
def set_agent_dead(self):
self._is_agent_dead = True
def update(self, tick, visible_tiles, visible_entities,
inventory=None, market=None, comm=None):
if self._is_agent_dead:
return
# cache has previous tick's data, so clear it
self.clear_cache()
# update the obs
self.current_tick = tick
self.tiles = visible_tiles # assert len(visible_tiles) == self.config.MAP_N_OBS
self.entities.update(visible_entities)
if self.config.ITEM_SYSTEM_ENABLED:
assert inventory is not None, "Inventory must be provided if ITEM_SYSTEM_ENABLED"
self.inventory.update(inventory)
if self.config.EXCHANGE_SYSTEM_ENABLED:
assert market is not None, "Market must be provided if EXCHANGE_SYSTEM_ENABLED"
self.market.update(market)
if self.config.COMMUNICATION_SYSTEM_ENABLED:
assert comm is not None, "Comm must be provided if COMMUNICATION_SYSTEM_ENABLED"
self.comm.update(comm)
# update helper vars
self.agent = self.entity(self.agent_id)
if self.config.COMBAT_SYSTEM_ENABLED:
latest_combat_tick = self.agent.latest_combat_tick
self.agent_in_combat = False if latest_combat_tick == 0 else \
(tick - latest_combat_tick) < self.config.COMBAT_STATUS_DURATION
else:
self.agent_in_combat = False
@lru_cache
def tile(self, r_delta, c_delta):
'''Return the array object corresponding to a nearby tile
Args:
r_delta: row offset from current agent
c_delta: col offset from current agent
Returns:
Vector corresponding to the specified tile
'''
idx_1d = (self.vision_radius+r_delta)*self.vision_diameter + self.vision_radius+c_delta
try:
return TileState.parse_array(self.tiles[idx_1d])
except IndexError:
return EMPTY_TILE
@lru_cache
def entity(self, entity_id):
rows = self.entities.values[self.entities.ids == entity_id]
if rows.shape[0] == 0:
return None
return EntityState.parse_array(rows[0])
def clear_cache(self):
# clear the outdated cache
self.entity.cache_clear()
self.tile.cache_clear()
def to_gym(self):
'''Convert the observation to a format that can be used by OpenAI Gym'''
if self.return_dummy_obs:
return self.empty_obs
self.gym_obs.clear(self.current_tick)
# NOTE: assume that all len(self.tiles) == self.config.MAP_N_OBS
self.gym_obs.set_arr_values('Tile', self.tiles)
self.gym_obs.set_arr_values('Entity', self.entities.values)
if self.config.ITEM_SYSTEM_ENABLED:
self.gym_obs.set_arr_values('Inventory', self.inventory.values)
if self.config.EXCHANGE_SYSTEM_ENABLED:
self.gym_obs.set_arr_values('Market', self.market.values)
if self.config.COMMUNICATION_SYSTEM_ENABLED:
self.gym_obs.set_arr_values('Communication', self.comm.values)
gym_obs = self.gym_obs.export()
if self.config.PROVIDE_ACTION_TARGETS:
gym_obs["ActionTargets"] = self._make_action_targets()
return gym_obs
def _make_action_targets(self):
self.action_targets.clear()
masks = self.action_targets.values
self._make_move_mask(masks["Move"])
if self.config.COMBAT_SYSTEM_ENABLED:
# Test below. see tests/core/test_observation_tile.py, test_action_target_consts()
# assert len(action.Style.edges) == 3
self._make_attack_mask(masks["Attack"])
if self.config.ITEM_SYSTEM_ENABLED:
self._make_use_mask(masks["Use"])
self._make_destroy_item_mask(masks["Destroy"])
self._make_give_mask(masks["Give"])
if self.config.EXCHANGE_SYSTEM_ENABLED:
self._make_sell_mask(masks["Sell"])
self._make_give_gold_mask(masks["GiveGold"])
self._make_buy_mask(masks["Buy"])
return masks
def _make_move_mask(self, move_mask, use_cython=None):
use_cython = use_cython or self.config.USE_CYTHON
if use_cython:
chp.make_move_mask(move_mask["Direction"], self.habitable_tiles,
self.agent.row, self.agent.col, ROW_DELTA, COL_DELTA)
return
move_mask["Direction"][:4] = self.habitable_tiles[self.agent.row+ROW_DELTA,
self.agent.col+COL_DELTA]
def _make_attack_mask(self, attack_mask, use_cython=None):
if self.config.COMBAT_ALLOW_FLEXIBLE_STYLE:
# NOTE: if the style is flexible, then the reach of all styles should be the same
assert self.config.COMBAT_MELEE_REACH == self.config.COMBAT_RANGE_REACH
assert self.config.COMBAT_MELEE_REACH == self.config.COMBAT_MAGE_REACH
assert self.config.COMBAT_RANGE_REACH == self.config.COMBAT_MAGE_REACH
if not self.config.COMBAT_SYSTEM_ENABLED or self.return_dummy_obs:
return
use_cython = use_cython or self.config.USE_CYTHON
if use_cython:
chp.make_attack_mask(
attack_mask["Target"], self.entities.values, EntityState.State.attr_name_to_col,
{"agent_id": self.agent_id, "row": self.agent.row, "col": self.agent.col,
"immunity": self.config.COMBAT_SPAWN_IMMUNITY,
"attack_range": self.config.COMBAT_RANGE_REACH})
return
# allow friendly fire but no self shooting
targetable = self.entities.ids != self.agent.id
# NOTE: this is a hack. Only target "normal" agents, which has npc_type of 0, 1, 2, 3
# For example, immortal "scout" agents has npc_type of -1
targetable &= self.entities.values[:,EntityState.State.attr_name_to_col["npc_type"]] >= 0
immunity = self.config.COMBAT_SPAWN_IMMUNITY
if self.agent.time_alive < immunity:
# NOTE: CANNOT attack players during immunity, thus mask should set to 0
targetable &= ~(self.entities.ids > 0) # ids > 0 equals entity.is_player
within_range = np.maximum( # calculating the l-inf dist
np.abs(self.entities.values[:,EntityState.State.attr_name_to_col["row"]] - self.agent.row),
np.abs(self.entities.values[:,EntityState.State.attr_name_to_col["col"]] - self.agent.col)
) <= self.config.COMBAT_MELEE_REACH
attack_mask["Target"][:self.entities.len] = targetable & within_range
if np.count_nonzero(attack_mask["Target"][:self.entities.len]):
# Mask the no-op option, since there should be at least one allowed move
# NOTE: this will make agents always attack if there is a valid target
attack_mask["Target"][-1] = 0
def _make_use_mask(self, use_mask):
# empty inventory -- nothing to use
if not (self.config.ITEM_SYSTEM_ENABLED and self.inventory.len > 0)\
or self.return_dummy_obs or self.agent_in_combat:
return
item_skill = self._item_skill()
not_listed = self.inventory.values[:,ItemState.State.attr_name_to_col["listed_price"]] == 0
item_type = self.inventory.values[:,ItemState.State.attr_name_to_col["type_id"]]
item_level = self.inventory.values[:,ItemState.State.attr_name_to_col["level"]]
# level limits are differently applied depending on item types
type_flt = np.tile(np.array(list(item_skill.keys())), (self.inventory.len,1))
level_flt = np.tile(np.array(list(item_skill.values())), (self.inventory.len,1))
item_type = np.tile(np.transpose(np.atleast_2d(item_type)), (1,len(item_skill)))
item_level = np.tile(np.transpose(np.atleast_2d(item_level)), (1,len(item_skill)))
level_satisfied = np.any((item_type==type_flt) & (item_level<=level_flt), axis=1)
use_mask["InventoryItem"][:self.inventory.len] = not_listed & level_satisfied
def _item_skill(self):
agent = self.agent
# the minimum agent level is 1
level = max(1, agent.melee_level, agent.range_level, agent.mage_level,
agent.fishing_level, agent.herbalism_level, agent.prospecting_level,
agent.carving_level, agent.alchemy_level)
return {
item_system.Hat.ITEM_TYPE_ID: level,
item_system.Top.ITEM_TYPE_ID: level,
item_system.Bottom.ITEM_TYPE_ID: level,
item_system.Spear.ITEM_TYPE_ID: agent.melee_level,
item_system.Bow.ITEM_TYPE_ID: agent.range_level,
item_system.Wand.ITEM_TYPE_ID: agent.mage_level,
item_system.Rod.ITEM_TYPE_ID: agent.fishing_level,
item_system.Gloves.ITEM_TYPE_ID: agent.herbalism_level,
item_system.Pickaxe.ITEM_TYPE_ID: agent.prospecting_level,
item_system.Axe.ITEM_TYPE_ID: agent.carving_level,
item_system.Chisel.ITEM_TYPE_ID: agent.alchemy_level,
item_system.Whetstone.ITEM_TYPE_ID: agent.melee_level,
item_system.Arrow.ITEM_TYPE_ID: agent.range_level,
item_system.Runes.ITEM_TYPE_ID: agent.mage_level,
item_system.Ration.ITEM_TYPE_ID: level,
item_system.Potion.ITEM_TYPE_ID: level
}
def _make_destroy_item_mask(self, destroy_mask):
# empty inventory -- nothing to destroy
if not (self.config.ITEM_SYSTEM_ENABLED and self.inventory.len > 0)\
or self.return_dummy_obs or self.agent_in_combat:
return
# not equipped items in the inventory can be destroyed
not_equipped = self.inventory.values[:,ItemState.State.attr_name_to_col["equipped"]] == 0
destroy_mask["InventoryItem"][:self.inventory.len] = not_equipped
def _make_give_mask(self, give_mask):
if not self.config.ITEM_SYSTEM_ENABLED or self.return_dummy_obs or self.agent_in_combat\
or self.inventory.len == 0:
return
# InventoryItem
not_equipped = self.inventory.values[:,ItemState.State.attr_name_to_col["equipped"]] == 0
not_listed = self.inventory.values[:,ItemState.State.attr_name_to_col["listed_price"]] == 0
give_mask["InventoryItem"][:self.inventory.len] = not_equipped & not_listed
# Give Target
# NOTE: Allow give to entities within visual range. So no distance check is needed
# entities_pos = self.entities.values[:,[EntityState.State.attr_name_to_col["row"],
# EntityState.State.attr_name_to_col["col"]]]
# same_tile = utils.linf(entities_pos, (self.agent.row, self.agent.col)) == 0
not_me = self.entities.ids != self.agent_id
player = (self.entities.values[:,EntityState.State.attr_name_to_col["npc_type"]] == 0)
give_mask["Target"][:self.entities.len] = player & not_me
def _make_sell_mask(self, sell_mask):
# empty inventory -- nothing to sell
if not (self.config.EXCHANGE_SYSTEM_ENABLED and self.inventory.len > 0) \
or self.return_dummy_obs or self.agent_in_combat:
return
not_equipped = self.inventory.values[:,ItemState.State.attr_name_to_col["equipped"]] == 0
not_listed = self.inventory.values[:,ItemState.State.attr_name_to_col["listed_price"]] == 0
sell_mask["InventoryItem"][:self.inventory.len] = not_equipped & not_listed
def _make_give_gold_mask(self, give_mask):
if not self.config.EXCHANGE_SYSTEM_ENABLED or self.return_dummy_obs or self.agent_in_combat\
or int(self.agent.gold) <= 2: # NOTE: this is a hack to reduce mask computation
return
# GiveGold Target
# NOTE: Allow give to entities within visual range. So no distance check is needed
# entities_pos = self.entities.values[:,[EntityState.State.attr_name_to_col["row"],
# EntityState.State.attr_name_to_col["col"]]]
# same_tile = utils.linf(entities_pos, (self.agent.row, self.agent.col)) == 0
not_me = self.entities.ids != self.agent_id
player = (self.entities.values[:,EntityState.State.attr_name_to_col["npc_type"]] == 0)
give_mask["Target"][:self.entities.len] = player & not_me
# GiveGold Amount (Price)
gold = int(self.agent.gold)
give_mask["Price"][gold:] = 0 # NOTE: Price masks starts with all ones
def _make_buy_mask(self, buy_mask):
if not self.config.EXCHANGE_SYSTEM_ENABLED or self.return_dummy_obs or self.agent_in_combat \
or self.market.len == 0:
return
market_items = self.market.values
not_mine = market_items[:,ItemState.State.attr_name_to_col["owner_id"]] != self.agent_id
# if the inventory is full, one can only buy existing ammo stack
# otherwise, one can buy anything owned by other, having enough money
if self.inventory.len >= self.config.ITEM_INVENTORY_CAPACITY:
exist_ammo_listings = self._existing_ammo_listings()
if not np.any(exist_ammo_listings):
return
not_mine &= exist_ammo_listings
enough_gold = market_items[:,ItemState.State.attr_name_to_col["listed_price"]] \
<= self.agent.gold
buy_mask["MarketItem"][:self.market.len] = not_mine & enough_gold
def _existing_ammo_listings(self):
sig_col = (ItemState.State.attr_name_to_col["type_id"],
ItemState.State.attr_name_to_col["level"])
ammo_id = [ammo.ITEM_TYPE_ID for ammo in
[item_system.Whetstone, item_system.Arrow, item_system.Runes]]
# search ammo stack from the inventory
type_flt = np.tile(np.array(ammo_id), (self.inventory.len,1))
item_type = np.tile(
np.transpose(np.atleast_2d(self.inventory.values[:,sig_col[0]])),
(1, len(ammo_id)))
exist_ammo = self.inventory.values[np.any(item_type == type_flt, axis=1)]
# self does not have ammo
if exist_ammo.shape[0] == 0:
return np.zeros(self.market.len, dtype=bool)
# search the existing ammo stack from the market that's not mine
type_flt = np.tile(np.array(exist_ammo[:,sig_col[0]]), (self.market.len,1))
level_flt = np.tile(np.array(exist_ammo[:,sig_col[1]]), (self.market.len,1))
item_type = np.tile(np.transpose(np.atleast_2d(self.market.values[:,sig_col[0]])),
(1, exist_ammo.shape[0]))
item_level = np.tile(np.transpose(np.atleast_2d(self.market.values[:,sig_col[1]])),
(1, exist_ammo.shape[0]))
exist_ammo_listings = np.any((item_type==type_flt) & (item_level==level_flt), axis=1)
not_mine = self.market.values[:,ItemState.State.attr_name_to_col["owner_id"]] != self.agent_id
return exist_ammo_listings & not_mine
================================================
FILE: nmmo/core/realm.py
================================================
from __future__ import annotations
from collections import defaultdict
from typing import Dict
import numpy as np
import nmmo
from nmmo.core.map import Map
from nmmo.core.tile import TileState
from nmmo.core.action import Action, Buy, Comm
from nmmo.entity.entity import EntityState
from nmmo.entity.entity_manager import PlayerManager
from nmmo.entity.npc_manager import NPCManager
from nmmo.datastore.numpy_datastore import NumpyDatastore
from nmmo.systems.exchange import Exchange
from nmmo.systems.item import ItemState
from nmmo.lib.event_log import EventLogger, EventState
from nmmo.render.replay_helper import ReplayHelper
def prioritized(entities: Dict, merged: Dict):
"""Sort actions into merged according to priority"""
for idx, actions in entities.items():
for atn, args in actions.items():
merged[atn.priority].append((idx, (atn, args.values())))
return merged
class Realm:
"""Top-level world object"""
def __init__(self, config, np_random):
self.config = config
self._np_random = np_random # rng
assert isinstance(
config, nmmo.config.Config
), f"Config {config} is not a config instance (did you pass the class?)"
Action.hook(config)
self.datastore = NumpyDatastore()
for s in [TileState, EntityState, ItemState, EventState]:
self.datastore.register_object_type(s._name, s.State.num_attributes)
self.tick = None # to use as a "reset" checker
# Load the world file
self.map = Map(config, self, self._np_random)
self.fog_map = np.zeros((config.MAP_SIZE, config.MAP_SIZE), dtype=np.float16)
# Event logger
self.event_log = EventLogger(self)
# Entity handlers
self.players = PlayerManager(self, self._np_random)
self.npcs = NPCManager(self, self._np_random)
# Global item registry
self.items = {}
# Global item exchange
self.exchange = Exchange(self)
# Replay helper
self._replay_helper = None
# Initialize actions
nmmo.Action.init(config)
def reset(self, np_random, map_dict,
custom_spawn=False,
seize_targets=None,
delete_dead_player=True):
"""Reset the sub-systems and load the provided map"""
self._np_random = np_random
self.tick = 0
self.update_fog_map(reset=True)
#self.event_log.reset()
self.items.clear()
self.exchange.reset()
if self._replay_helper is not None:
self._replay_helper.reset()
# Load the map np array into the map, tiles and reset
self.map.reset(map_dict, self._np_random, seize_targets)
# EntityState and ItemState tables must be empty after players/npcs.reset()
self.players.reset(self._np_random, delete_dead_player)
self.npcs.reset(self._np_random)
# assert EntityState.State.table(self.datastore).is_empty(), \
# "EntityState table is not empty"
# assert ItemState.State.table(self.datastore).is_empty(), \
# "ItemState table is not empty"
# DataStore id allocator must be reset to be deterministic
EntityState.State.table(self.datastore).reset()
ItemState.State.table(self.datastore).reset()
self.event_log.reset() # reset this last for debugging
if custom_spawn is False:
# NOTE: custom spawning npcs and agents can be done outside, after reset()
self.npcs.default_spawn()
self.players.spawn()
def packet(self):
"""Client packet"""
return {
"environment": self.map.repr,
"border": self.config.MAP_BORDER,
"size": self.config.MAP_SIZE,
"resource": self.map.packet,
"player": self.players.packet,
"npc": self.npcs.packet,
"market": self.exchange.packet,
}
@property
def num_players(self):
"""Number of alive player agents"""
return len(self.players.entities)
@property
def seize_status(self):
return self.map.seize_status
def entity(self, ent_id):
e = self.entity_or_none(ent_id)
assert e is not None, f"Entity {ent_id} does not exist"
return e
def entity_or_none(self, ent_id):
if ent_id is None:
return None
"""Get entity by ID"""
if ent_id < 0:
return self.npcs.get(ent_id)
return self.players.get(ent_id)
def step(self, actions):
"""Run game logic for one tick
Args:
actions: Dict of agent actions
Returns:
dead: List of dead agents
"""
# Prioritize actions
npc_actions = self.npcs.actions()
merged = defaultdict(list)
prioritized(actions, merged)
prioritized(npc_actions, merged)
# Update entities and perform actions
self.players.update(actions)
self.npcs.update(npc_actions)
# Execute actions -- CHECK ME the below priority
# - 10: Use - equip ammo, restore HP, etc.
# - 20: Buy - exchange while sellers, items, buyers are all intact
# - 30: Give, GiveGold - transfer while both are alive and at the same tile
# - 40: Destroy - use with SELL/GIVE, if not gone, destroy and recover space
# - 50: Attack
# - 60: Move
# - 70: Sell - to guarantee the listed items are available to buy
# - 99: Comm
for priority in sorted(merged):
# TODO: we should be randomizing these, otherwise the lower ID agents
# will always go first. --> ONLY SHUFFLE BUY
if priority == Buy.priority:
self._np_random.shuffle(merged[priority])
# CHECK ME: do we need this line?
# ent_id, (atn, args) = merged[priority][0]
for ent_id, (atn, args) in merged[priority]:
ent = self.entity(ent_id)
if (ent.alive and not ent.status.frozen) or \
(ent.is_recon and priority == Comm.priority): # recons can always comm
atn.call(self, ent, *args)
dead_players = self.players.cull()
dead_npcs = self.npcs.cull()
self.tick += 1
# These require the updated tick
self.map.step()
self.update_fog_map()
self.exchange.step()
self.event_log.update()
if self._replay_helper is not None:
self._replay_helper.update()
return dead_players, dead_npcs
def update_fog_map(self, reset=False):
fog_start_tick = self.config.DEATH_FOG_ONSET
if fog_start_tick is None:
return
fog_speed = self.config.DEATH_FOG_SPEED
center = self.config.MAP_SIZE // 2
safe = self.config.DEATH_FOG_FINAL_SIZE
if reset:
dist = -self.config.MAP_BORDER
for i in range(center):
l, r = i, self.config.MAP_SIZE - i
# positive value represents the poison strength
# negative value represents the shortest distance to poison area
self.fog_map[l:r, l:r] = -dist
dist += 1
# mark the safe area
self.fog_map[center-safe:center+safe+1, center-safe:center+safe+1] = -self.config.MAP_SIZE
return
# consider the map border so that the fog can hit the border at fog_start_tick
if self.tick >= fog_start_tick:
self.fog_map += fog_speed
# mark the safe area
self.fog_map[center-safe:center+safe+1, center-safe:center+safe+1] = -self.config.MAP_SIZE
def record_replay(self, replay_helper: ReplayHelper) -> ReplayHelper:
self._replay_helper = replay_helper
self._replay_helper.set_realm(self)
return replay_helper
================================================
FILE: nmmo/core/terrain.py
================================================
import os
import logging
import numpy as np
from imageio.v2 import imread, imsave
from scipy import stats
from nmmo.lib import material, seeding, utils, vec_noise
def sharp(noise):
'''Exponential noise sharpener for perlin ridges'''
return 2 * (0.5 - abs(0.5 - noise))
class Save:
'''Save utility for map files'''
@staticmethod
def render(mats, lookup, path):
'''Render tiles to png'''
images = [[lookup[e] for e in l] for l in mats]
image = np.vstack([np.hstack(e) for e in images])
imsave(path, image)
@staticmethod
def fractal(terrain, path):
'''Save fractal to both png and npy'''
imsave(os.path.join(path, 'fractal.png'), (256*terrain).astype(np.uint8))
np.save(os.path.join(path, 'fractal.npy'), terrain.astype(np.float16))
@staticmethod
def as_numpy(mats, path):
'''Save map to .npy'''
path = os.path.join(path, 'map.npy')
np.save(path, mats.astype(int))
# pylint: disable=E1101:no-member
# Terrain uses setattr()
class Terrain:
'''Terrain material class; populated at runtime'''
@staticmethod
def generate_terrain(config, map_id, interpolaters):
center = config.MAP_CENTER
border = config.MAP_BORDER
size = config.MAP_SIZE
frequency = config.TERRAIN_FREQUENCY
offset = config.TERRAIN_FREQUENCY_OFFSET
octaves = center // config.TERRAIN_TILES_PER_OCTAVE
#Compute a unique seed based on map index
#Flip seed used to ensure train/eval maps are different
seed = map_id + 1
if config.TERRAIN_FLIP_SEED:
seed = -seed
#Log interpolation factor
if not interpolaters:
interpolaters = np.logspace(config.TERRAIN_LOG_INTERPOLATE_MIN,
config.TERRAIN_LOG_INTERPOLATE_MAX, config.MAP_N)
interpolate = interpolaters[map_id]
#Data buffers
val = np.zeros((size, size, octaves))
scale = np.zeros((size, size, octaves))
s = np.arange(size)
X, Y = np.meshgrid(s, s)
#Compute noise over logscaled octaves
start = frequency
end = min(start, start - np.log2(center) + offset)
for idx, freq in enumerate(np.logspace(start, end, octaves, base=2)):
val[:, :, idx] = vec_noise.snoise2(seed*size + freq*X, idx*size + freq*Y)
#Compute L1 distance
l1 = utils.l1_map(size)
#Interpolation Weights
rrange = np.linspace(-1, 1, 2*octaves-1)
pdf = stats.norm.pdf(rrange, 0, interpolate)
pdf = pdf / max(pdf)
high = center / 2
delta = high / octaves
#Compute perlin mask
noise = np.zeros((size, size))
X, Y = np.meshgrid(s, s)
expand = int(np.log2(center)) - 2
for idx, octave in enumerate(range(expand, 1, -1)):
freq, mag = 1 / 2**octave, 1 / 2**idx
noise += mag * vec_noise.snoise2(seed*size + freq*X, idx*size + freq*Y)
noise -= np.min(noise)
noise = octaves * noise / np.max(noise) - 1e-12
noise = noise.astype(int)
#Compute L1 and Perlin scale factor
for i in range(octaves):
start = octaves - i - 1
scale[l1 <= high] = np.arange(start, start + octaves)
high -= delta
start = noise - 1
l1_scale = np.clip(l1, 0, size//2 - border - 2)
l1_scale = l1_scale / np.max(l1_scale)
for i in range(octaves):
idxs = l1_scale*scale[:, :, i] + (1-l1_scale)*(start + i)
scale[:, :, i] = pdf[idxs.astype(int)]
#Blend octaves
std = np.std(val)
val = val / std
val = scale * val
val = np.sum(scale * val, -1)
val = std * val / np.std(val)
val = 0.5 + np.clip(val, -1, 1)/2
# Transform fractal noise to terrain
matl = fractal_to_material(config, val)
matl = process_map_border(config, matl, l1)
return val, matl, interpolaters
def fractal_to_material(config, fractal, all_grass=False):
size = config.MAP_SIZE
matl_map = np.zeros((size, size), dtype=np.int16)
for y in range(size):
for x in range(size):
if all_grass:
matl_map[y, x] = Terrain.GRASS
continue
v = fractal[y, x]
if v <= config.TERRAIN_WATER:
mat = Terrain.WATER
elif v <= config.TERRAIN_GRASS:
mat = Terrain.GRASS
elif v <= config.TERRAIN_FOILAGE:
mat = Terrain.FOILAGE
else:
mat = Terrain.STONE
matl_map[y, x] = mat
return matl_map
def process_map_border(config, matl_map, l1=None):
size = config.MAP_SIZE
border = config.MAP_BORDER
if l1 is None:
l1 = utils.l1_map(size)
# Void and grass border
matl_map[l1 > size/2 - border] = material.Void.index
matl_map[l1 == size//2 - border] = material.Grass.index
edge = l1 == size//2 - border - 1
stone = (matl_map == material.Stone.index) | (matl_map == material.Water.index)
matl_map[edge & stone] = material.Foilage.index
return matl_map
def place_fish(tiles, mmin, mmax, np_random, num_fish):
placed = 0
# if USE_CYTHON:
# water_loc = chp.tile_where(tiles, Terrain.WATER, mmin, mmax)
# else:
water_loc = np.where(tiles == Terrain.WATER)
water_loc = [(r, c) for r, c in zip(water_loc[0], water_loc[1])
if mmin < r < mmax and mmin < c < mmax]
if len(water_loc) < num_fish:
raise RuntimeError('Not enough water tiles to place fish.')
np_random.shuffle(water_loc)
allow = {Terrain.GRASS} # Fish should be placed adjacent to grass
for r, c in water_loc:
if tiles[r-1, c] in allow or tiles[r+1, c] in allow or \
tiles[r, c-1] in allow or tiles[r, c+1] in allow:
tiles[r, c] = Terrain.FISH
placed += 1
if placed == num_fish:
break
if placed < num_fish:
raise RuntimeError('Could not find the water tile to place fish.')
def uniform(config, tiles, mat, mmin, mmax, np_random):
r = np_random.integers(mmin, mmax)
c = np_random.integers(mmin, mmax)
if tiles[r, c] not in {Terrain.GRASS}:
uniform(config, tiles, mat, mmin, mmax, np_random)
else:
tiles[r, c] = mat
def cluster(config, tiles, mat, mmin, mmax, np_random):
mmin = mmin + 1
mmax = mmax - 1
r = np_random.integers(mmin, mmax)
c = np_random.integers(mmin, mmax)
matls = {Terrain.GRASS}
if tiles[r, c] not in matls:
cluster(config, tiles, mat, mmin-1, mmax+1, np_random)
return
tiles[r, c] = mat
if tiles[r-1, c] in matls:
tiles[r-1, c] = mat
if tiles[r+1, c] in matls:
tiles[r+1, c] = mat
if tiles[r, c-1] in matls:
tiles[r, c-1] = mat
if tiles[r, c+1] in matls:
tiles[r, c+1] = mat
def spawn_profession_resources(config, tiles, np_random=None):
if np_random is None:
np_random = np.random
mmin = config.MAP_BORDER + 1
mmax = config.MAP_SIZE - config.MAP_BORDER - 1
for _ in range(config.PROGRESSION_SPAWN_CLUSTERS):
cluster(config, tiles, Terrain.ORE, mmin, mmax, np_random)
cluster(config, tiles, Terrain.TREE, mmin, mmax, np_random)
cluster(config, tiles, Terrain.CRYSTAL, mmin, mmax, np_random)
for _ in range(config.PROGRESSION_SPAWN_UNIFORMS):
uniform(config, tiles, Terrain.HERB, mmin, mmax, np_random)
place_fish(tiles, mmin, mmax, np_random,
config.PROGRESSION_SPAWN_UNIFORMS)
def try_add_tile(map_tiles, row, col, tile_to_add):
if map_tiles[row, col] == Terrain.GRASS:
map_tiles[row, col] = tile_to_add
return True
return False
def scatter_extra_resources(config, tiles, np_random=None,
density_factor=6):
if np_random is None:
np_random = np.random
center = config.MAP_CENTER
mmin = config.MAP_BORDER + 1
mmax = config.MAP_SIZE - config.MAP_BORDER - 1
water_to_add, water_added = (center//density_factor)**2, 0
food_to_add, food_added = (center//density_factor)**2, 0
while True:
if water_added >= water_to_add and food_added >= food_to_add:
break
r, c = tuple(np_random.integers(mmin, mmax, size=(2,)))
if water_added < water_to_add:
water_added += 1 if try_add_tile(tiles, r, c, Terrain.WATER) else 0
if food_added < food_to_add:
food_added += 1 if try_add_tile(tiles, r, c, Terrain.FOILAGE) else 0
class MapGenerator:
'''Procedural map generation'''
def __init__(self, config):
self.config = config
self.load_textures()
self.interpolaters = None
def load_textures(self):
'''Called during setup; loads and resizes tile pngs'''
lookup = {}
path = self.config.PATH_TILE
scale = self.config.MAP_PREVIEW_DOWNSCALE
for mat in material.All:
key = mat.tex
tex = imread(path.format(key))
lookup[mat.index] = tex[:, :, :3][::scale, ::scale]
setattr(Terrain, key.upper(), mat.index)
self.textures = lookup
def generate_all_maps(self, seed=None):
'''Generates MAP_N maps according to generate_map
Provides additional utilities for saving to .npy and rendering png previews'''
config = self.config
np_random, _ = seeding.np_random(seed)
#Only generate if maps are not cached
path_maps = os.path.join(config.PATH_CWD, config.PATH_MAPS)
os.makedirs(path_maps, exist_ok=True)
existing_maps = set(map_dir + '/map.npy' for map_dir in os.listdir(path_maps))
if not config.MAP_FORCE_GENERATION and existing_maps:
required_maps = {
f'map{idx}/map.npy' for idx in range(1, config.MAP_N+1)
}
missing = required_maps - existing_maps
if not missing:
return
if __debug__:
logging.info('Generating %s maps', str(config.MAP_N))
for idx in range(config.MAP_N):
path = path_maps + '/map' + str(idx+1)
os.makedirs(path, exist_ok=True)
terrain, tiles = self.generate_map(idx, np_random)
#Save/render
Save.as_numpy(tiles, path)
Save.fractal(terrain, path)
if config.MAP_GENERATE_PREVIEWS:
b = config.MAP_BORDER
tiles = [e[b:-b+1] for e in tiles][b:-b+1]
Save.render(tiles, self.textures, path+'/map.png')
def generate_map(self, idx, np_random=None):
'''Generate a single map
The default method is a relatively complex multiscale perlin noise method.
This is not just standard multioctave noise -- we are seeding multioctave noise
itself with perlin noise to create localized deviations in scale, plus additional
biasing to decrease terrain frequency towards the center of the map
We found that this creates more visually interesting terrain and more deviation in
required planning horizon across different parts of the map. This is by no means a
gold-standard: you are free to override this method and create customized terrain
generation more suitable for your application. Simply pass MAP_GENERATOR=YourMapGenClass
as a config argument.'''
config = self.config
if config.TERRAIN_SYSTEM_ENABLED:
if not hasattr(self, 'interpolaters'):
self.interpolaters = None
terrain, tiles, _ = Terrain.generate_terrain(config, idx, self.interpolaters)
else:
size = config.MAP_SIZE
terrain = np.zeros((size, size))
tiles = np.zeros((size, size), dtype=object)
for r in range(size):
for c in range(size):
linf = max(abs(r - size//2), abs(c - size//2))
if linf <= size//2 - config.MAP_BORDER:
tiles[r, c] = Terrain.GRASS
else:
tiles[r, c] = Terrain.VOID
if config.PROFESSION_SYSTEM_ENABLED:
spawn_profession_resources(config, tiles, np_random)
return terrain, tiles
================================================
FILE: nmmo/core/tile.py
================================================
from types import SimpleNamespace
from nmmo.datastore.serialized import SerializedState
from nmmo.lib import material, event_code
# pylint: disable=no-member,protected-access
TileState = SerializedState.subclass(
"Tile", [
"row",
"col",
"material_id",
])
TileState.Limits = lambda config: {
"row": (0, config.MAP_SIZE-1),
"col": (0, config.MAP_SIZE-1),
"material_id": (0, config.MAP_N_TILE),
}
TileState.Query = SimpleNamespace(
window=lambda ds, r, c, radius: ds.table("Tile").window(
TileState.State.attr_name_to_col["row"],
TileState.State.attr_name_to_col["col"],
r, c, radius),
get_map=lambda ds, map_size:
ds.table("Tile")._data[1:(map_size*map_size+1)]
.reshape((map_size,map_size,len(TileState.State.attr_name_to_col)))
)
class Tile(TileState):
def __init__(self, realm, r, c, np_random):
super().__init__(realm.datastore, TileState.Limits(realm.config))
self.realm = realm
self.config = realm.config
self._np_random = np_random
self.row.update(r)
self.col.update(c)
self.state = None
self.material = None
self.depleted = False
self.entities = {}
self.seize_history = []
@property
def occupied(self):
# NOTE: ONLY players consider whether the tile is occupied or not
# NPCs can move into occupied tiles.
# Surprisingly, this has huge effect on training, so be careful.
# Tried this -- "sum(1 for ent_id in self.entities if ent_id > 0) > 0"
return len(self.entities) > 0
@property
def repr(self):
return ((self.row.val, self.col.val))
@property
def pos(self):
return self.row.val, self.col.val
@property
def habitable(self):
return self.material in material.Habitable
@property
def impassible(self):
return self.material in material.Impassible
@property
def void(self):
return self.material == material.Void
@property
def tex(self):
return self.state.tex
def reset(self, mat, config, np_random):
self._np_random = np_random # reset the RNG
self.entities = {}
self.seize_history.clear()
self.material = mat(config)
self._respawn()
def set_depleted(self):
self.depleted = True
self.state = self.material.deplete
self.material_id.update(self.state.index)
def _respawn(self):
self.depleted = False
self.state = self.material
self.material_id.update(self.state.index)
def add_entity(self, ent):
assert ent.ent_id not in self.entities
self.entities[ent.ent_id] = ent
def remove_entity(self, ent_id):
assert ent_id in self.entities
self.entities.pop(ent_id)
def step(self):
if not self.depleted or self.material.respawn == 0:
return
if self._np_random.random() < self.material.respawn:
self._respawn()
def harvest(self, deplete):
assert not self.depleted, f'{self.state} is depleted'
assert self.state in material.Harvestable, f'{self.state} not harvestable'
if deplete:
self.set_depleted()
return self.material.harvest()
def update_seize(self):
if len(self.entities) != 1: # only one entity can seize a tile
return
ent_id, entity = list(self.entities.items())[0]
if ent_id < 0: # not counting npcs
return
team_members = entity.my_task.assignee # NOTE: only one task per player
if self.seize_history and self.seize_history[-1][0] in team_members:
# no need to add another entry if the last entry is from the same team (incl. self)
return
self.seize_history.append((ent_id, self.realm.tick))
if self.realm.event_log:
self.realm.event_log.record(event_code.EventCode.SEIZE_TILE, entity, tile=self.pos)
================================================
FILE: nmmo/datastore/__init__.py
================================================
================================================
FILE: nmmo/datastore/datastore.py
================================================
from __future__ import annotations
from typing import Dict, List
from nmmo.datastore.id_allocator import IdAllocator
"""
This code defines a data storage system that allows for the
creation, manipulation, and querying of records.
The DataTable class serves as the foundation for the data
storage, providing methods for updating and retrieving data,
as well as filtering and querying records.
The DatastoreRecord class represents a single record within
a table and provides a simple interface for interacting with
the data. The Datastore class serves as the main entry point
for the data storage system, allowing for the creation and
management of tables and records.
The implementation of the DataTable class is left to the
developer, but the DatastoreRecord and Datastore classes
should be sufficient for most use cases.
See numpy_datastore.py for an implementation.
"""
class DataTable:
def __init__(self, num_columns: int):
self._num_columns = num_columns
self._id_allocator = IdAllocator(100)
def reset(self):
self._id_allocator = IdAllocator(100)
def update(self, row_id: int, col: int, value):
raise NotImplementedError
def get(self, ids: List[id]):
raise NotImplementedError
def where_in(self, col: int, values: List):
raise NotImplementedError
def where_eq(self, col: str, value):
raise NotImplementedError
def where_neq(self, col: str, value):
raise NotImplementedError
def window(self, row_idx: int, col_idx: int, row: int, col: int, radius: int):
raise NotImplementedError
def remove_row(self, row_id: int):
raise NotImplementedError
def add_row(self) -> int:
raise NotImplementedError
def is_empty(self) -> bool:
raise NotImplementedError
class DatastoreRecord:
def __init__(self, datastore, table: DataTable, row_id: int) -> None:
self.datastore = datastore
self.table = table
self.id = row_id
def update(self, col: int, value):
self.table.update(self.id, col, value)
def get(self, col: int):
return self.table.get(self.id)[col]
def delete(self):
self.table.remove_row(self.id)
class Datastore:
def __init__(self) -> None:
self._tables: Dict[str, DataTable] = {}
def register_object_type(self, object_type: str, num_colums: int):
if object_type not in self._tables:
self._tables[object_type] = self._create_table(num_colums)
def create_record(self, object_type: str) -> DatastoreRecord:
table = self._tables[object_type]
row_id = table.add_row()
return DatastoreRecord(self, table, row_id)
def table(self, object_type: str) -> DataTable:
return self._tables[object_type]
def _create_table(self, num_columns: int) -> DataTable:
raise NotImplementedError
================================================
FILE: nmmo/datastore/id_allocator.py
================================================
from ordered_set import OrderedSet
class IdAllocator:
def __init__(self, max_id):
# Key 0 is reserved as padding
self.max_id = 1
self.free = OrderedSet()
self.expand(max_id)
def full(self):
return len(self.free) == 0
def remove(self, row_id):
self.free.add(row_id)
def allocate(self):
return self.free.pop(0)
def expand(self, max_id):
self.free.update(range(self.max_id, max_id))
self.max_id = max_id
================================================
FILE: nmmo/datastore/numpy_datastore.py
================================================
from typing import List
import numpy as np
from nmmo.datastore.datastore import Datastore, DataTable
class NumpyTable(DataTable):
def __init__(self, num_columns: int, initial_size: int, dtype=np.int16):
super().__init__(num_columns)
self._dtype = dtype
self._initial_size = initial_size
self._max_rows = 0
self._data = np.zeros((0, self._num_columns), dtype=self._dtype)
self._expand(self._initial_size)
def reset(self):
super().reset() # resetting _id_allocator
self._max_rows = 0
self._data = np.zeros((0, self._num_columns), dtype=self._dtype)
self._expand(self._initial_size)
def update(self, row_id: int, col: int, value):
self._data[row_id, col] = value
def get(self, ids: List[int]):
return self._data[ids]
def where_eq(self, col: int, value):
return self._data[self._data[:,col] == value]
def where_neq(self, col: int, value):
return self._data[self._data[:,col] != value]
def where_gt(self, col: int, value):
return self._data[self._data[:,col] > value]
def where_in(self, col: int, values: List):
return self._data[np.in1d(self._data[:,col], values)]
def window(self, row_idx: int, col_idx: int, row: int, col: int, radius: int):
return self._data[(
(np.abs(self._data[:,row_idx] - row) <= radius) &
(np.abs(self._data[:,col_idx] - col) <= radius)
).ravel()]
def add_row(self) -> int:
if self._id_allocator.full():
self._expand(self._max_rows * 2)
row_id = self._id_allocator.allocate()
return row_id
def remove_row(self, row_id: int) -> int:
self._id_allocator.remove(row_id)
self._data[row_id] = 0
def _expand(self, max_rows: int):
assert max_rows > self._max_rows
data = np.zeros((max_rows, self._num_columns), dtype=self._dtype)
data[:self._max_rows] = self._data
self._max_rows = max_rows
self._id_allocator.expand(max_rows)
self._data = data
def is_empty(self) -> bool:
all_data_zero = np.all(self._data == 0)
# 0th row is reserved as padding, so # of free ids is _max_rows-1
all_id_free = len(self._id_allocator.free) == self._max_rows-1
return all_data_zero and all_id_free
class NumpyDatastore(Datastore):
def _create_table(self, num_columns: int) -> DataTable:
return NumpyTable(num_columns, 100)
================================================
FILE: nmmo/datastore/serialized.py
================================================
# pylint: disable=bare-except,c-extension-no-member
from __future__ import annotations
from ast import Tuple
import math
from types import SimpleNamespace
from typing import Dict, List
from nmmo.datastore.datastore import Datastore, DatastoreRecord
try:
import nmmo.lib.cython_helper as chp
USE_CYTHON = True
except:
USE_CYTHON = False
"""
This code defines classes for serializing and deserializing data
in a structured way.
The SerializedAttribute class represents a single attribute of a
record and provides methods for updating and querying its value,
as well as enforcing minimum and maximum bounds on the value.
The SerializedState class serves as a base class for creating
serialized representations of specific types of data, using a
list of attribute names to define the structure of the data.
The subclass method is a factory method for creating subclasses
of SerializedState that are tailored to specific types of data.
"""
class SerializedAttribute():
def __init__(self,
name: str,
datastore_record: DatastoreRecord,
column: int, min_val=-math.inf, max_val=math.inf) -> None:
self._name = name
self.datastore_record = datastore_record
self._column = column
self._min = min_val
self._max = max_val
self._val = 0
@property
def val(self):
return self._val
def update(self, value):
if value > self._max:
value = self._max
elif value < self._min:
value = self._min
self.datastore_record.update(self._column, value)
self._val = value
@property
def min(self):
return self._min
@property
def max(self):
return self._max
def increment(self, val=1, max_v=math.inf):
self.update(min(max_v, self.val + val))
return self
def decrement(self, val=1, min_v=-math.inf):
self.update(max(min_v, self.val - val))
return self
@property
def empty(self):
return self.val == 0
def __eq__(self, other):
return self.val == other
def __ne__(self, other):
return self.val != other
def __lt__(self, other):
return self.val < other
def __le__(self, other):
return self.val <= other
def __gt__(self, other):
return self.val > other
def __ge__(self, other):
return self.val >= other
class SerializedState():
@staticmethod
def subclass(name: str, attributes: List[str]):
class Subclass(SerializedState):
_name = name
State = SimpleNamespace(
attr_name_to_col = {a: i for i, a in enumerate(attributes)},
num_attributes = len(attributes),
table = lambda ds: ds.table(name)
)
def __init__(self, datastore: Datastore,
limits: Dict[str, Tuple[float, float]] = None):
limits = limits or {}
self.datastore_record = datastore.create_record(name)
for attr, col in self.State.attr_name_to_col.items():
try:
setattr(self, attr,
SerializedAttribute(attr, self.datastore_record, col,
*limits.get(attr, (-math.inf, math.inf))))
except Exception as exc:
raise RuntimeError('Failed to set attribute "' + attr + '"') from exc
@classmethod
def parse_array(cls, data) -> SimpleNamespace:
# Takes in a data array and returns a SimpleNamespace object with
# attribute names as keys and corresponding values from the input
# data array.
assert len(data) == cls.State.num_attributes, \
f"Expected {cls.State.num_attributes} attributes, got {len(data)}"
if USE_CYTHON:
return chp.parse_array(data, cls.State.attr_name_to_col)
return SimpleNamespace(**{
attr: data[col] for attr, col in cls.State.attr_name_to_col.items()
})
return Subclass
================================================
FILE: nmmo/entity/__init__.py
================================================
from nmmo.entity.entity import Entity
from nmmo.entity.player import Player
================================================
FILE: nmmo/entity/entity.py
================================================
import math
from types import SimpleNamespace
import numpy as np
from nmmo.datastore.serialized import SerializedState
from nmmo.systems import inventory
from nmmo.lib.event_code import EventCode
# pylint: disable=no-member
EntityState = SerializedState.subclass(
"Entity", [
"id",
"npc_type", # 1 - passive, 2 - neutral, 3 - aggressive
"row",
"col",
# Status
"damage",
"time_alive",
"freeze",
"item_level",
"attacker_id",
"latest_combat_tick",
"message",
# Resources
"gold",
"health",
"food",
"water",
# Combat Skills
"melee_level",
"melee_exp",
"range_level",
"range_exp",
"mage_level",
"mage_exp",
# Harvest Skills
"fishing_level",
"fishing_exp",
"herbalism_level",
"herbalism_exp",
"prospecting_level",
"prospecting_exp",
"carving_level",
"carving_exp",
"alchemy_level",
"alchemy_exp",
])
EntityState.Limits = lambda config: {
**{
"id": (-math.inf, math.inf),
"npc_type": (-1, 3), # -1 for immortal
"row": (0, config.MAP_SIZE-1),
"col": (0, config.MAP_SIZE-1),
"damage": (0, math.inf),
"time_alive": (0, math.inf),
"freeze": (0, math.inf),
"item_level": (0, math.inf),
"attacker_id": (-np.inf, math.inf),
"latest_combat_tick": (0, math.inf),
"health": (0, config.PLAYER_BASE_HEALTH),
},
**({
"message": (0, config.COMMUNICATION_NUM_TOKENS),
} if config.COMMUNICATION_SYSTEM_ENABLED else {}),
**({
"gold": (0, math.inf),
"food": (0, config.RESOURCE_BASE),
"water": (0, config.RESOURCE_BASE),
} if config.RESOURCE_SYSTEM_ENABLED else {}),
**({
"melee_level": (0, config.PROGRESSION_LEVEL_MAX),
"melee_exp": (0, math.inf),
"range_level": (0, config.PROGRESSION_LEVEL_MAX),
"range_exp": (0, math.inf),
"mage_level": (0, config.PROGRESSION_LEVEL_MAX),
"mage_exp": (0, math.inf),
"fishing_level": (0, config.PROGRESSION_LEVEL_MAX),
"fishing_exp": (0, math.inf),
"herbalism_level": (0, config.PROGRESSION_LEVEL_MAX),
"herbalism_exp": (0, math.inf),
"prospecting_level": (0, config.PROGRESSION_LEVEL_MAX),
"prospecting_exp": (0, math.inf),
"carving_level": (0, config.PROGRESSION_LEVEL_MAX),
"carving_exp": (0, math.inf),
"alchemy_level": (0, config.PROGRESSION_LEVEL_MAX),
"alchemy_exp": (0, math.inf),
} if config.PROGRESSION_SYSTEM_ENABLED else {}),
}
EntityState.State.comm_attr_map = {name: EntityState.State.attr_name_to_col[name]
for name in ["id", "row", "col", "message"]}
CommAttr = np.array(list(EntityState.State.comm_attr_map.values()), dtype=np.int64)
EntityState.Query = SimpleNamespace(
# Whole table
table=lambda ds: ds.table("Entity").where_neq(
EntityState.State.attr_name_to_col["id"], 0),
# Single entity
by_id=lambda ds, id: ds.table("Entity").where_eq(
EntityState.State.attr_name_to_col["id"], id)[0],
# Multiple entities
by_ids=lambda ds, ids: ds.table("Entity").where_in(
EntityState.State.attr_name_to_col["id"], ids),
# Entities in a radius
window=lambda ds, r, c, radius: ds.table("Entity").window(
EntityState.State.attr_name_to_col["row"],
EntityState.State.attr_name_to_col["col"],
r, c, radius),
# Communication obs
comm_obs=lambda ds: ds.table("Entity").where_gt(
EntityState.State.attr_name_to_col["id"], 0)[:, CommAttr]
)
class Resources:
def __init__(self, ent, config):
self.config = config
self.health = ent.health
self.water = ent.water
self.food = ent.food
self.health_restore = 0
self.resilient = False
self.health.update(config.PLAYER_BASE_HEALTH)
if config.RESOURCE_SYSTEM_ENABLED:
self.water.update(config.RESOURCE_BASE)
self.food.update(config.RESOURCE_BASE)
def update(self, immortal=False):
if not self.config.RESOURCE_SYSTEM_ENABLED or immortal:
return
regen = self.config.RESOURCE_HEALTH_RESTORE_FRACTION
thresh = self.config.RESOURCE_HEALTH_REGEN_THRESHOLD
food_thresh = self.food > thresh * self.config.RESOURCE_BASE
water_thresh = self.water > thresh * self.config.RESOURCE_BASE
org_health = self.health.val
if food_thresh and water_thresh:
restore = np.floor(self.health.max * regen)
self.health.increment(restore)
if self.food.empty:
starvation_damage = self.config.RESOURCE_STARVATION_RATE
if self.resilient:
starvation_damage *= self.config.RESOURCE_DAMAGE_REDUCTION
self.health.decrement(int(starvation_damage))
if self.water.empty:
dehydration_damage = self.config.RESOURCE_DEHYDRATION_RATE
if self.resilient:
dehydration_damage *= self.config.RESOURCE_DAMAGE_REDUCTION
self.health.decrement(int(dehydration_damage))
# records both increase and decrease in health due to food and water
self.health_restore = self.health.val - org_health
def packet(self):
data = {}
data['health'] = { 'val': self.health.val, 'max': self.config.PLAYER_BASE_HEALTH }
data['food'] = data['water'] = { 'val': 0, 'max': 0 }
if self.config.RESOURCE_SYSTEM_ENABLED:
data['food'] = { 'val': self.food.val, 'max': self.config.RESOURCE_BASE }
data['water'] = { 'val': self.water.val, 'max': self.config.RESOURCE_BASE }
return data
class Status:
def __init__(self, ent):
self.freeze = ent.freeze
def update(self):
if self.frozen:
self.freeze.decrement(1)
def packet(self):
data = {}
data['freeze'] = self.freeze.val
return data
@property
def frozen(self):
return self.freeze.val > 0
# NOTE: History.packet() is actively used in visulazing attacks
class History:
def __init__(self, ent):
self.actions = {}
self.attack = None
self.starting_position = ent.pos
self.exploration = 0
self.player_kills = 0
self.damage_received = 0
self.damage_inflicted = 0
self.damage = ent.damage
self.time_alive = ent.time_alive
self.last_pos = None
def update(self, entity, actions):
self.attack = None
self.damage.update(0)
self.actions = {}
if entity.ent_id in actions:
self.actions = actions[entity.ent_id]
self.time_alive.increment()
def packet(self):
data = {}
data['damage'] = self.damage.val
data['timeAlive'] = self.time_alive.val
data['damage_inflicted'] = self.damage_inflicted
data['damage_received'] = self.damage_received
if self.attack is not None:
data['attack'] = self.attack
# NOTE: the client seems to use actions for visualization
# but produces errors with the new actions. So we comment out these for now
# actions = {}
# for atn, args in self.actions.items():
# atn_packet = {}
# # Avoid recursive player packet
# if atn.__name__ == 'Attack':
# continue
# for key, val in args.items():
# if hasattr(val, 'packet'):
# atn_packet[key.__name__] = val.packet
# else:
# atn_packet[key.__name__] = val.__name__
# actions[atn.__name__] = atn_packet
# data['actions'] = actions
data['actions'] = {}
return data
# pylint: disable=no-member
class Entity(EntityState):
def __init__(self, realm, pos, entity_id, name):
super().__init__(realm.datastore, EntityState.Limits(realm.config))
self.realm = realm
self.config = realm.config
# TODO: do not access realm._np_random directly
# related to the whole NPC, scripted logic
# pylint: disable=protected-access
self._np_random = realm._np_random
self.policy = name
self.repr = None
self.name = name + str(entity_id)
self._pos = None
self.set_pos(*pos)
self.ent_id = entity_id
self.id.update(entity_id)
self.vision = self.config.PLAYER_VISION_RADIUS
self.attacker = None
self.target = None
self.closest = None
self.spawn_pos = pos
self._immortal = False # used for testing/player recon
self._recon = False
# Submodules
self.status = Status(self)
self.history = History(self)
self.resources = Resources(self, self.config)
self.inventory = inventory.Inventory(realm, self)
# @property
# def ent_id(self):
# return self.id.val
def packet(self):
data = {}
data['status'] = self.status.packet()
data['history'] = self.history.packet()
data['inventory'] = self.inventory.packet()
data['alive'] = self.alive
data['base'] = {
'r': self.pos[0],
'c': self.pos[1],
'name': self.name,
'level': self.attack_level,
'item_level': self.item_level.val,}
return data
def update(self, realm, actions):
'''Update occurs after actions, e.g. does not include history'''
self._pos = None
if self.history.damage == 0:
self.attacker = None
self.attacker_id.update(0)
if realm.config.EQUIPMENT_SYSTEM_ENABLED:
self.item_level.update(self.equipment.total(lambda e: e.level))
self.status.update()
self.history.update(self, actions)
# Returns True if the entity is alive
def receive_damage(self, source, dmg):
self.history.damage_received += dmg
self.history.damage.update(dmg)
self.resources.health.decrement(dmg)
if self.alive:
return True
# at this point, self is dead
if source:
source.history.player_kills += 1
self.realm.event_log.record(EventCode.PLAYER_KILL, source, target=self)
# if self is dead, unlist its items from the market regardless of looting
if self.config.EXCHANGE_SYSTEM_ENABLED:
for item in list(self.inventory.items):
self.realm.exchange.unlist_item(item)
# if self is dead but no one can loot, destroy its items
if source is None or not source.is_player: # nobody or npcs cannot loot
if self.config.ITEM_SYSTEM_ENABLED:
for item in list(self.inventory.items):
item.destroy()
return False
# now, source can loot the dead self
return False
# pylint: disable=unused-argument
def apply_damage(self, dmg, style):
self.history.damage_inflicted += dmg
@property
def pos(self):
if self._pos is None:
self._pos = (self.row.val, self.col.val)
return self._pos
def set_pos(self, row, col):
self._pos = (row, col)
self.row.update(row)
self.col.update(col)
@property
def alive(self):
return self.resources.health.val > 0
@property
def immortal(self):
return self._immortal
@property
def is_player(self) -> bool:
return False
@property
def is_npc(self) -> bool:
return False
@property
def is_recon(self):
return self._recon
@property
def attack_level(self) -> int:
melee = self.skills.melee.level.val
ranged = self.skills.range.level.val
mage = self.skills.mage.level.val
return int(max(melee, ranged, mage))
@property
def in_combat(self) -> bool:
# NOTE: the initial latest_combat_tick is 0, and valid values are greater than 0
if not self.config.COMBAT_SYSTEM_ENABLED or self.latest_combat_tick.val == 0:
return False
return (self.realm.tick - self.latest_combat_tick.val) < self.config.COMBAT_STATUS_DURATION
================================================
FILE: nmmo/entity/entity_manager.py
================================================
from collections.abc import Mapping
from typing import Dict
from nmmo.entity.entity import Entity, EntityState
from nmmo.entity.player import Player
from nmmo.lib import spawn, event_code
class EntityGroup(Mapping):
def __init__(self, realm, np_random):
self.datastore = realm.datastore
self.realm = realm
self.config = realm.config
self._np_random = np_random
self._entity_table = EntityState.Query.table(self.datastore)
self.entities: Dict[int, Entity] = {}
self.dead_this_tick: Dict[int, Entity] = {}
self._delete_dead_entity = True # is default
def __len__(self):
return len(self.entities)
def __contains__(self, e):
return e in self.entities
def __getitem__(self, key) -> Entity:
return self.entities[key]
def __iter__(self) -> Entity:
yield from self.entities
def items(self):
return self.entities.items()
@property
def corporeal(self):
return {**self.entities, **self.dead_this_tick}
@property
def packet(self):
return {k: v.packet() for k, v in self.corporeal.items()}
def reset(self, np_random, delete_dead_entity=True):
self._np_random = np_random # reset the RNG
self._delete_dead_entity = delete_dead_entity
for ent in self.entities.values():
# destroy the items
if self.config.ITEM_SYSTEM_ENABLED:
for item in list(ent.inventory.items):
item.destroy()
ent.datastore_record.delete()
self.entities.clear()
self.dead_this_tick.clear()
def spawn_entity(self, entity):
pos, ent_id = entity.pos, entity.id.val
self.realm.map.tiles[pos].add_entity(entity)
self.entities[ent_id] = entity
def cull_entity(self, entity):
pos, ent_id = entity.pos, entity.id.val
self.realm.map.tiles[pos].remove_entity(ent_id)
self.entities.pop(ent_id)
# destroy the remaining items (of starved/dehydrated players)
# of the agents who don't go through receive_damage()
if self.config.ITEM_SYSTEM_ENABLED:
for item in list(entity.inventory.items):
item.destroy()
if ent_id > 0:
self.realm.event_log.record(event_code.EventCode.AGENT_CULLED, entity)
def cull(self):
self.dead_this_tick.clear()
for ent in [ent for ent in self.entities.values() if not ent.alive]:
self.dead_this_tick[ent.ent_id] = ent
self.cull_entity(ent)
if self._delete_dead_entity:
ent.datastore_record.delete()
return self.dead_this_tick
def update(self, actions):
# # batch updates
# # time_alive, damage are from entity.py, History.update()
# ent_idx = self._entity_table[:, EntityState.State.attr_name_to_col["id"]] != 0
# self._entity_table[ent_idx, EntityState.State.attr_name_to_col["time_alive"]] += 1
# self._entity_table[ent_idx, EntityState.State.attr_name_to_col["damage"]] = 0
# # freeze from entity.py, Status.update()
# freeze_idx = self._entity_table[:, EntityState.State.attr_name_to_col["freeze"]] > 0
# self._entity_table[freeze_idx, EntityState.State.attr_name_to_col["freeze"]] -= 1
for entity in self.entities.values():
entity.update(self.realm, actions)
class PlayerManager(EntityGroup):
def spawn(self, agent_loader: spawn.SequentialLoader = None):
if agent_loader is None:
agent_loader = self.config.PLAYER_LOADER(self.config, self._np_random)
# Check and assign the reslient flag
resilient_flag = [False] * self.config.PLAYER_N
if self.config.RESOURCE_SYSTEM_ENABLED:
num_resilient = round(self.config.RESOURCE_RESILIENT_POPULATION * self.config.PLAYER_N)
for idx in range(num_resilient):
resilient_flag[idx] = self.config.RESOURCE_DAMAGE_REDUCTION > 0
self._np_random.shuffle(resilient_flag)
# Spawn the players
for agent_id in self.config.POSSIBLE_AGENTS:
r, c = agent_loader.get_spawn_position(agent_id)
if agent_id in self.entities:
continue
# NOTE: put spawn_individual() here. Is a separate function necessary?
agent = next(agent_loader) # get agent cls from config.PLAYERS
agent = agent(self.config, agent_id)
player = Player(self.realm, (r, c), agent, resilient_flag[agent_id-1])
super().spawn_entity(player)
================================================
FILE: nmmo/entity/npc.py
================================================
import numpy as np
from nmmo.entity import entity
from nmmo.core import action as Action
from nmmo.systems import combat, droptable
from nmmo.systems import item as Item
from nmmo.systems import skill
from nmmo.systems.inventory import EquipmentSlot
from nmmo.lib.event_code import EventCode
from nmmo.lib import utils, astar
DIRECTIONS = [ # row delta, col delta, action
(-1, 0, Action.North),
(1, 0, Action.South),
(0, -1, Action.West),
(0, 1, Action.East)] * 2
DELTA_TO_DIR = {(r, c): atn for r, c, atn in DIRECTIONS}
DELTA_TO_DIR[(0, 0)] = None
def get_habitable_dir(ent):
r, c = ent.pos
is_habitable = ent.realm.map.habitable_tiles
start = ent._np_random.get_direction() # pylint: disable=protected-access
for i in range(4):
delta_r, delta_c, direction = DIRECTIONS[start + i]
if is_habitable[r + delta_r, c + delta_c]:
return direction
return Action.North
def meander_toward(ent, goal, dist_crit=10, toward_weight=3):
r, c = ent.pos
delta_r, delta_c = goal[0] - r, goal[1] - c
abs_dr, abs_dc = abs(delta_r), abs(delta_c)
dist_l1 = abs_dr + abs_dc
# If close (less than dist_crit), use expensive aStar
if dist_l1 <= dist_crit:
delta = astar.aStar(ent.realm.map, ent.pos, goal)
return move_action(DELTA_TO_DIR[delta] if delta in DELTA_TO_DIR else None)
# Otherwise, use a weighted random walk
cand_dirs = []
weights = []
for i in range(4):
r_offset, c_offset, direction = DIRECTIONS[i]
if ent.realm.map.habitable_tiles[r + r_offset, c + c_offset]:
cand_dirs.append(direction)
weights.append(1)
if r_offset * delta_r > 0:
weights[-1] += toward_weight * abs_dr/dist_l1
if c_offset * delta_c > 0:
weights[-1] += toward_weight * abs_dc/dist_l1
if len(cand_dirs) == 0:
return move_action(Action.North)
if len(cand_dirs) == 1:
return move_action(cand_dirs[0])
weights = np.array(weights)
# pylint: disable=protected-access
return move_action(ent._np_random.choice(cand_dirs, p=weights/np.sum(weights)))
def move_action(direction):
return {Action.Move: {Action.Direction: direction}} if direction else {}
class Equipment:
def __init__(self, total,
melee_attack, range_attack, mage_attack,
melee_defense, range_defense, mage_defense):
self.level = total
self.ammunition = EquipmentSlot()
self.melee_attack = melee_attack
self.range_attack = range_attack
self.mage_attack = mage_attack
self.melee_defense = melee_defense
self.range_defense = range_defense
self.mage_defense = mage_defense
def total(self, getter):
return getter(self)
# pylint: disable=R0801
# Similar lines here and in inventory.py
@property
def packet(self):
packet = {}
packet["item_level"] = self.total
packet["melee_attack"] = self.melee_attack
packet["range_attack"] = self.range_attack
packet["mage_attack"] = self.mage_attack
packet["melee_defense"] = self.melee_defense
packet["range_defense"] = self.range_defense
packet["mage_defense"] = self.mage_defense
return packet
# pylint: disable=no-member
class NPC(entity.Entity):
def __init__(self, realm, pos, iden, name, npc_type):
super().__init__(realm, pos, iden, name)
self.skills = skill.Combat(realm, self)
self.realm = realm
self.last_action = None
self.droptable = None
self.spawn_danger = None
self.equipment = None
self.npc_type.update(npc_type)
@property
def is_npc(self) -> bool:
return True
def update(self, realm, actions):
super().update(realm, actions)
if not self.alive:
return
self.resources.health.increment(1)
self.last_action = actions
def can_see(self, target):
if target is None or target.immortal:
return False
distance = utils.linf_single(self.pos, target.pos)
return distance <= self.vision
def _move_toward(self, goal):
delta = astar.aStar(self.realm.map, self.pos, goal)
return move_action(DELTA_TO_DIR[delta] if delta in DELTA_TO_DIR else None)
def _meander(self):
return move_action(get_habitable_dir(self))
def can_attack(self, target):
if target is None or not self.config.NPC_SYSTEM_ENABLED or target.immortal:
return False
if not self.config.NPC_ALLOW_ATTACK_OTHER_NPCS and target.is_npc:
return False
distance = utils.linf_single(self.pos, target.pos)
return distance <= self.skills.style.attack_range(self.realm.config)
def _has_target(self, search=False):
if self.target and (not self.target.alive or not self.can_see(self.target)):
self.target = None
# NOTE: when attacked by several agents, this will always target the last attacker
if self.attacker and self.target is None:
self.target = self.attacker
if self.target is None and search is True:
self.target = utils.identify_closest_target(self)
return self.target
def _add_attack_action(self, actions, target):
actions.update({Action.Attack: {Action.Style: self.skills.style, Action.Target: target}})
def _charge_toward(self, target):
actions = self._move_toward(target.pos)
if self.can_attack(target):
self._add_attack_action(actions, target)
return actions
# Returns True if the entity is alive
def receive_damage(self, source, dmg):
if super().receive_damage(source, dmg):
return True
# run the next lines if the npc is killed
# source receive gold & items in the droptable
# pylint: disable=no-member
if self.gold.val > 0:
source.gold.increment(self.gold.val)
self.realm.event_log.record(EventCode.LOOT_GOLD, source, amount=self.gold.val, target=self)
self.gold.update(0)
if self.droptable:
for item in self.droptable.roll(self.realm, self.attack_level):
if source.is_player and source.inventory.space:
# inventory.receive() returns True if the item is received
# if source does not have space, inventory.receive() destroys the item
if source.inventory.receive(item):
self.realm.event_log.record(EventCode.LOOT_ITEM, source, item=item, target=self)
else:
item.destroy()
return False
@staticmethod
def default_spawn(realm, pos, iden, np_random, danger=None):
config = realm.config
# check the position
if realm.map.tiles[pos].impassible:
return None
# Select AI Policy
danger = danger or combat.danger(config, pos)
if danger >= config.NPC_SPAWN_AGGRESSIVE:
ent = Aggressive(realm, pos, iden)
elif danger >= config.NPC_SPAWN_NEUTRAL:
ent = PassiveAggressive(realm, pos, iden)
elif danger >= config.NPC_SPAWN_PASSIVE:
ent = Passive(realm, pos, iden)
else:
return None
ent.spawn_danger = danger
# Select combat focus
style = np_random.integers(0,3)
if style == 0:
style = Action.Melee
elif style == 1:
style = Action.Range
else:
style = Action.Mage
ent.skills.style = style
# Compute level
level = 0
if config.PROGRESSION_SYSTEM_ENABLED:
level_min = config.NPC_LEVEL_MIN
level_max = config.NPC_LEVEL_MAX
level = int(danger * (level_max - level_min) + level_min)
# Set skill levels
if style == Action.Melee:
ent.skills.melee.set_experience_by_level(level)
elif style == Action.Range:
ent.skills.range.set_experience_by_level(level)
elif style == Action.Mage:
ent.skills.mage.set_experience_by_level(level)
# Gold
if config.EXCHANGE_SYSTEM_ENABLED:
# pylint: disable=no-member
ent.gold.update(level)
ent.droptable = droptable.Standard()
# Equipment to instantiate
if config.EQUIPMENT_SYSTEM_ENABLED:
lvl = level - np_random.random()
ilvl = int(5 * lvl)
level_damage = config.NPC_LEVEL_DAMAGE * config.NPC_LEVEL_MULTIPLIER
level_defense = config.NPC_LEVEL_DEFENSE * config.NPC_LEVEL_MULTIPLIER
offense = int(config.NPC_BASE_DAMAGE + lvl * level_damage)
defense = int(config.NPC_BASE_DEFENSE + lvl * level_defense)
ent.equipment = Equipment(ilvl, offense, offense, offense, defense, defense, defense)
armor = [Item.Hat, Item.Top, Item.Bottom]
ent.droptable.add(np_random.choice(armor))
if config.PROFESSION_SYSTEM_ENABLED:
tools = [Item.Rod, Item.Gloves, Item.Pickaxe, Item.Axe, Item.Chisel]
ent.droptable.add(np_random.choice(tools))
return ent
def packet(self):
data = super().packet()
data["skills"] = self.skills.packet()
data["resource"] = { "health": {
"val": self.resources.health.val, "max": self.config.PLAYER_BASE_HEALTH } }
return data
class Passive(NPC):
def __init__(self, realm, pos, iden, name=None):
super().__init__(realm, pos, iden, name or "Passive", 1)
def decide(self):
# Move only, no attack
return self._meander()
class PassiveAggressive(NPC):
def __init__(self, realm, pos, iden, name=None):
super().__init__(realm, pos, iden, name or "Neutral", 2)
def decide(self):
if self._has_target() is None:
return self._meander()
return self._charge_toward(self.target)
class Aggressive(NPC):
def __init__(self, realm, pos, iden, name=None):
super().__init__(realm, pos, iden, name or "Hostile", 3)
def decide(self):
if self._has_target(search=True) is None:
return self._meander()
return self._charge_toward(self.target)
class Soldier(NPC):
def __init__(self, realm, pos, iden, name, order):
super().__init__(realm, pos, iden, name or "Soldier", 3) # Hostile with order
self.target_entity = None
self.rally_point = None
self._process_order(order)
def _process_order(self, order):
if order is None:
return
if "destroy" in order: # destroy the specified entity id
self.target_entity = self.realm.entity(order["destroy"])
if "rally" in order:
# rally until spotting an enemy
self.rally_point = order["rally"] # (row, col)
def _is_order_done(self, radius=5):
if self.target_entity and not self.target_entity.alive:
self.target_entity = None
if self.rally_point and utils.linf_single(self.pos, self.rally_point) <= radius:
self.rally_point = None
def decide(self):
self._is_order_done()
# NOTE: destroying the target entity is the highest priority
if self.target_entity is None and self._has_target(search=True):
if self.can_attack(self.target):
return self._charge_toward(self.target)
actions = self._decide_move_action()
self._decide_attack_action(actions)
return actions
def _decide_move_action(self):
# in the order of priority
if self.target_entity:
return self._move_toward(self.target_entity.pos)
if self.target:
# If it"s close enough, it will use A*. Otherwise, random.
return meander_toward(self, self.target.pos)
if self.rally_point:
return meander_toward(self, self.rally_point)
return self._meander()
def _decide_attack_action(self, actions):
# The default is to attack the target entity, if within range
if self.target_entity and self.can_attack(self.target_entity):
self._add_attack_action(actions, self.target_entity)
elif self.can_attack(self.target):
self._add_attack_action(actions, self.target)
================================================
FILE: nmmo/entity/npc_manager.py
================================================
from typing import Callable
from nmmo.entity.entity_manager import EntityGroup
from nmmo.entity.npc import NPC, Soldier, Aggressive, PassiveAggressive, Passive
from nmmo.core import action
from nmmo.systems import combat
from nmmo.lib import spawn
class NPCManager(EntityGroup):
def __init__(self, realm, np_random):
super().__init__(realm, np_random)
self.next_id = -1
self.spawn_dangers = []
def reset(self, np_random):
super().reset(np_random)
self.next_id = -1
self.spawn_dangers.clear()
def actions(self):
return {idx: entity.decide() for idx, entity in self.entities.items()}
def default_spawn(self):
config = self.config
if not config.NPC_SYSTEM_ENABLED:
return
for _ in range(config.NPC_SPAWN_ATTEMPTS):
if len(self.entities) >= config.NPC_N:
break
if len(self.spawn_dangers) > 0:
danger = self.spawn_dangers.pop(0) # FIFO
r, c = combat.spawn(config, danger, self._np_random)
else:
center = config.MAP_CENTER
border = self.config.MAP_BORDER
# pylint: disable=unbalanced-tuple-unpacking
r, c = self._np_random.integers(border, center+border, 2).tolist()
npc = NPC.default_spawn(self.realm, (r, c), self.next_id, self._np_random)
if npc:
super().spawn_entity(npc)
self.next_id -= 1
def spawn_npc(self, r, c, danger=None, name=None, order=None,
apply_beta_to_danger=True):
if not self.realm.map.tiles[r, c].habitable:
return None
if danger and apply_beta_to_danger:
danger = min(1.0, max(0.0, danger)) # normalize
danger = self._np_random.beta(10*danger+0.01, 10.01-10*danger) # beta cannot take 0
if danger is None:
npc = Soldier(self.realm, (r, c), self.next_id, name, order)
elif danger >= self.config.NPC_SPAWN_AGGRESSIVE:
npc = Aggressive(self.realm, (r, c), self.next_id, name)
elif danger >= self.config.NPC_SPAWN_NEUTRAL:
npc = PassiveAggressive(self.realm, (r, c), self.next_id, name)
elif danger >= self.config.NPC_SPAWN_PASSIVE:
npc = Passive(self.realm, (r, c), self.next_id, name)
else:
return None
if npc:
super().spawn_entity(npc)
self.next_id -= 1
# NOTE: randomly set the combat style. revisit later
npc.skills.style = self._np_random.choice([action.Melee, action.Range, action.Mage])
return npc
def area_spawn(self, r_min, r_max, c_min, c_max, num_spawn,
npc_init_fn: Callable):
assert r_min < r_max and c_min < c_max, "Invalid area"
assert num_spawn > 0, "Invalid number of spawns"
while num_spawn > 0:
r = self._np_random.integers(r_min, r_max+1)
c = self._np_random.integers(c_min, c_max+1)
if npc_init_fn(r, c):
num_spawn -= 1
def edge_spawn(self, num_spawn, npc_init_fn: Callable):
assert num_spawn > 0, "Invalid number of spawns"
edge_locs = spawn.get_edge_tiles(self.config, self._np_random, shuffle=True)
assert len(edge_locs) >= num_spawn, "Not enough edge locations"
while num_spawn > 0:
r, c = edge_locs.pop()
npc = npc_init_fn(r, c)
if npc:
num_spawn -= 1
================================================
FILE: nmmo/entity/player.py
================================================
from nmmo.systems.skill import Skills
from nmmo.entity import entity
from nmmo.lib.event_code import EventCode
from nmmo.lib import spawn
# pylint: disable=no-member
class Player(entity.Entity):
def __init__(self, realm, pos, agent, resilient=False):
super().__init__(realm, pos, agent.iden, agent.policy)
self.agent = agent
self._immortal = realm.config.IMMORTAL
self.resources.resilient = resilient
self.my_task = None
self._make_mortal_tick = None # set to realm.tick when the player is made mortal
# Scripted hooks
self.target = None
self.vision = 7
# Logs
self.buys = 0
self.sells = 0
self.ration_consumed = 0
self.poultice_consumed = 0
self.ration_level_consumed = 0
self.poultice_level_consumed = 0
# initialize skills with the base level
self.skills = Skills(realm, self)
if realm.config.PROGRESSION_SYSTEM_ENABLED:
for skill in self.skills.skills:
skill.level.update(realm.config.PROGRESSION_BASE_LEVEL)
# Gold: initialize with 1 gold (EXCHANGE_BASE_GOLD).
# If the base amount is more than 1, alss check the npc's init gold.
if realm.config.EXCHANGE_SYSTEM_ENABLED:
self.gold.update(realm.config.EXCHANGE_BASE_GOLD)
@property
def serial(self):
return self.ent_id
@property
def is_player(self) -> bool:
return True
@property
def level(self) -> int:
# a player's level is the max of all skills
# CHECK ME: the initial level is 1 because of Basic skills,
# which are harvesting food/water and don't progress
return max(e.level.val for e in self.skills.skills)
def _set_immortal(self, value=True, duration=None):
self._immortal = value
# NOTE: a hack to mark the player as immortal in action targets
self.npc_type.update(-1 if value else 0)
if value and duration is not None:
self._make_mortal_tick = self.realm.tick + duration
if value is False:
self._make_mortal_tick = None
def make_recon(self, new_pos=None):
# NOTE: scout cannot act and cannot die
self.status.freeze.update(self.config.MAX_HORIZON)
self._set_immortal()
self._recon = True
if new_pos is not None:
if self.ent_id in self.realm.map.tiles[self.pos].entities:
self.realm.map.tiles[self.pos].remove_entity(self.ent_id)
self.realm.map.tiles[new_pos].add_entity(self)
self.set_pos(*new_pos)
def apply_damage(self, dmg, style):
super().apply_damage(dmg, style)
self.skills.apply_damage(style)
# TODO(daveey): The returns for this function are a mess
def receive_damage(self, source, dmg):
if self.immortal:
return False
# super().receive_damage returns True if self is alive after taking dmg
if super().receive_damage(source, dmg):
return True
if not self.config.ITEM_SYSTEM_ENABLED:
return False
# starting from here, source receive gold & inventory items
if self.config.EXCHANGE_SYSTEM_ENABLED and source is not None:
if self.gold.val > 0:
source.gold.increment(self.gold.val)
self.realm.event_log.record(EventCode.LOOT_GOLD, source, amount=self.gold.val, target=self)
self.gold.update(0)
# TODO: make source receive the highest-level items first
# because source cannot take it if the inventory is full
item_list = list(self.inventory.items)
self._np_random.shuffle(item_list)
for item in item_list:
self.inventory.remove(item)
# if source is None or NPC, destroy the item
if source.is_player:
# inventory.receive() returns True if the item is received
# if source doesn't have space, inventory.receive() destroys the item
if source.inventory.receive(item):
self.realm.event_log.record(EventCode.LOOT_ITEM, source, item=item, target=self)
else:
item.destroy()
# CHECK ME: this is an empty function. do we still need this?
self.skills.receive_damage(dmg)
return False
@property
def equipment(self):
return self.inventory.equipment
def packet(self):
data = super().packet()
data['entID'] = self.ent_id
data['resource'] = self.resources.packet()
data['skills'] = self.skills.packet()
data['inventory'] = self.inventory.packet()
# added for the 2.0 web client
data["metrics"] = {
"PlayerDefeats": self.history.player_kills,
"TimeAlive": self.time_alive.val,
"Gold": self.gold.val,
"DamageTaken": self.history.damage_received,}
return data
def update(self, realm, actions):
'''Post-action update. Do not include history'''
super().update(realm, actions)
# Spawn battle royale style death fog
# Starts at 0 damage on the specified config tick
# Moves in from the edges by 1 damage per tile per tick
# So after 10 ticks, you take 10 damage at the edge and 1 damage
# 10 tiles in, 0 damage in farther
# This means all agents will be force killed around
# MAP_CENTER / 2 + 100 ticks after spawning
fog = self.config.DEATH_FOG_ONSET
if fog is not None and self.realm.tick >= fog:
dmg = self.realm.fog_map[self.pos]
if dmg > 0.5: # fog_map has float values
self.receive_damage(None, round(dmg))
if not self.alive:
return
if self.config.PLAYER_HEALTH_INCREMENT > 0:
self.resources.health.increment(self.config.PLAYER_HEALTH_INCREMENT)
self.resources.update(self.immortal)
self.skills.update()
if self._make_mortal_tick is not None and self.realm.tick >= self._make_mortal_tick:
self._set_immortal(False)
def resurrect(self, health_prop=0.5, freeze_duration=10, edge_spawn=True):
# Respawn dead players at the edge
assert not self.alive, "Player is not dead"
self.status.freeze.update(freeze_duration)
self.resources.health.update(self.config.PLAYER_BASE_HEALTH*health_prop)
if self.config.RESOURCE_SYSTEM_ENABLED:
self.resources.water.update(self.config.RESOURCE_BASE)
self.resources.food.update(self.config.RESOURCE_BASE)
if edge_spawn:
new_spawn_pos = spawn.get_random_coord(self.config, self._np_random, edge=True)
else:
while True:
new_spawn_pos = spawn.get_random_coord(self.config, self._np_random, edge=False)
if self.realm.map.tiles[new_spawn_pos].habitable:
break
self.set_pos(*new_spawn_pos)
self.message.update(0)
self.realm.players.spawn_entity(self) # put back to the system
self._set_immortal(duration=freeze_duration)
if self.my_task and len(self.my_task.assignee) == 1:
# NOTE: Only one task per agent is supported for now
# Agent's task progress need to be reset ONLY IF the task is an agent task
self.my_task.reset()
================================================
FILE: nmmo/lib/__init__.py
================================================
================================================
FILE: nmmo/lib/astar.py
================================================
#pylint: disable=invalid-name
import heapq
from nmmo.lib.utils import in_bounds
CUTOFF = 100
def l1(start, goal):
sr, sc = start
gr, gc = goal
return abs(gr - sr) + abs(gc - sc)
def adjacentPos(pos):
r, c = pos
return [(r - 1, c), (r, c - 1), (r + 1, c), (r, c + 1)]
def aStar(realm_map, start, goal, cutoff = CUTOFF):
tiles = realm_map.tiles
if start == goal:
return (0, 0)
if (start, goal) in realm_map.pathfinding_cache:
return realm_map.pathfinding_cache[(start, goal)]
initial_goal = goal
pq = [(0, start)]
backtrace = {}
cost = {start: 0}
closestPos = start
closestHeuristic = l1(start, goal)
closestCost = closestHeuristic
while pq:
# Use approximate solution if budget exhausted
cutoff -= 1
if cutoff <= 0:
if goal not in backtrace:
goal = closestPos
break
priority, cur = heapq.heappop(pq)
if cur == goal:
break
for nxt in adjacentPos(cur):
if not in_bounds(*nxt, tiles.shape) or realm_map.habitable_tiles[nxt] == 0:
continue
newCost = cost[cur] + 1
if nxt not in cost or newCost < cost[nxt]:
cost[nxt] = newCost
heuristic = l1(goal, nxt)
priority = newCost + heuristic
# Compute approximate solution
if heuristic < closestHeuristic or (
heuristic == closestHeuristic and priority < closestCost):
closestPos = nxt
closestHeuristic = heuristic
closestCost = priority
heapq.heappush(pq, (priority, nxt))
backtrace[nxt] = cur
while goal in backtrace and backtrace[goal] != start:
gr, gc = goal
goal = backtrace[goal]
sr, sc = goal
realm_map.pathfinding_cache[(goal, initial_goal)] = (gr - sr, gc - sc)
sr, sc = start
gr, gc = goal
realm_map.pathfinding_cache[(start, initial_goal)] = (gr - sr, gc - sc)
return (gr - sr, gc - sc)
# End A*
================================================
FILE: nmmo/lib/colors.py
================================================
# pylint: disable=all
#Various Enums used for handling materials, entity types, etc.
#Data texture pairs are used for enums that require textures.
#These textures are filled in by the Render class at run time.
import numpy as np
import colorsys
def rgb(h):
h = h.lstrip('#')
return tuple(int(h[i:i+2], 16) for i in (0, 2, 4))
def rgbNorm(h):
h = h.lstrip('#')
return tuple(int(h[i:i+2], 16)/255.0 for i in (0, 2, 4))
def makeColor(idx, h=1, s=1, v=1):
r, g, b = colorsys.hsv_to_rgb(h, s, v)
rgbval = tuple(int(255*e) for e in [r, g, b])
hexval = '%02x%02x%02x' % rgbval
return Color(str(idx), hexval)
class Color:
def __init__(self, name, hexVal):
self.name = name
self.hex = hexVal
self.rgb = rgb(hexVal)
self.norm = rgbNorm(hexVal)
self.value = self.rgb #Emulate enum
def packet(self):
return self.hex
class Color256:
def make256():
parh, parv = np.meshgrid(np.linspace(0.075, 1, 16), np.linspace(0.25, 1, 16)[::-1])
parh, parv = parh.T.ravel(), parv.T.ravel()
idxs = np.arange(256)
params = zip(idxs, parh, parv)
colors = [makeColor(idx, h=h, s=1, v=v) for idx, h, v in params]
return colors
colors = make256()
class Color16:
def make():
hues = np.linspace(0, 1, 16)
idxs = np.arange(256)
params = zip(idxs, hues)
colors = [makeColor(idx, h=h, s=1, v=1) for idx, h in params]
return colors
colors = make()
class Tier:
BLACK = Color('BLACK', '#000000')
WOOD = Color('WOOD', '#784d1d')
BRONZE = Color('BRONZE', '#db4508')
SILVER = Color('SILVER', '#dedede')
GOLD = Color('GOLD', '#ffae00')
PLATINUM = Color('PLATINUM', '#cd75ff')
DIAMOND = Color('DIAMOND', '#00bbbb')
class Swatch:
def colors():
'''Return list of swatch colors'''
return
def rand():
'''Return random swatch color'''
all_colors = Swatch.colors()
randInd = np.random.randint(0, len(all_colors))
return all_colors[randInd]
class Neon(Swatch):
RED = Color('RED', '#ff0000')
ORANGE = Color('ORANGE', '#ff8000')
YELLOW = Color('YELLOW', '#ffff00')
GREEN = Color('GREEN', '#00ff00')
MINT = Color('MINT', '#00ff80')
CYAN = Color('CYAN', '#00ffff')
BLUE = Color('BLUE', '#0000ff')
PURPLE = Color('PURPLE', '#8000ff')
MAGENTA = Color('MAGENTA', '#ff00ff')
FUCHSIA = Color('FUCHSIA', '#ff0080')
SPRING = Color('SPRING', '#80ff80')
SKY = Color('SKY', '#0080ff')
WHITE = Color('WHITE', '#ffffff')
GRAY = Color('GRAY', '#666666')
BLACK = Color('BLACK', '#000000')
BLOOD = Color('BLOOD', '#bb0000')
BROWN = Color('BROWN', '#7a3402')
GOLD = Color('GOLD', '#eec600')
SILVER = Color('SILVER', '#b8b8b8')
TERM = Color('TERM', '#41ff00')
MASK = Color('MASK', '#d67fff')
def colors():
return (
Neon.CYAN, Neon.MINT, Neon.GREEN,
Neon.BLUE, Neon.PURPLE, Neon.MAGENTA,
Neon.FUCHSIA, Neon.SPRING, Neon.SKY,
Neon.RED, Neon.ORANGE, Neon.YELLOW)
class Solid(Swatch):
BLUE = Color('BLUE', '#1f77b4')
ORANGE = Color('ORANGE', '#ff7f0e')
GREEN = Color('GREEN', '#2ca02c')
RED = Color('RED', '#D62728')
PURPLE = Color('PURPLE', '#9467bd')
BROWN = Color('BROWN', '#8c564b')
PINK = Color('PINK', '#e377c2')
GREY = Color('GREY', '#7f7f7f')
CHARTREUSE = Color('CHARTREUSE', '#bcbd22')
SKY = Color('SKY', '#17becf')
def colors():
return (
Solid.BLUE, Solid.ORANGE, Solid.GREEN,
Solid.RED, Solid.PURPLE, Solid.BROWN,
Solid.PINK, Solid.CHARTREUSE, Solid.SKY,
Solid.GREY)
class Palette:
def __init__(self, initial_swatch=Neon):
self.colors = {}
for idx, color in enumerate(initial_swatch.colors()):
self.colors[idx] = color
def color(self, idx):
if idx in self.colors:
return self.colors[idx]
color = makeColor(idx, h=np.random.rand(), s=1, v=1)
self.colors[idx] = color
return color
================================================
FILE: nmmo/lib/cython_helper.pyx
================================================
#cython: boundscheck=True
#cython: wraparound=True
#cython: nonecheck=True
from types import SimpleNamespace
import numpy as np
cimport numpy as cnp
# for array indexing
cnp.import_array()
def make_move_mask(cnp.ndarray[cnp.int8_t] mask,
cnp.ndarray[cnp.int8_t, ndim=2] habitable_tiles,
short row, short col,
cnp.ndarray[cnp.int64_t] row_delta,
cnp.ndarray[cnp.int64_t] col_delta):
for i in range(4):
mask[i] = habitable_tiles[row_delta[i] + row, col_delta[i] + col]
# NOTE: assume that incoming mask are all zeros
def make_attack_mask(cnp.ndarray[cnp.int8_t] mask,
cnp.ndarray[cnp.int16_t, ndim=2] entities,
dict entity_attr,
dict my_info):
cdef short idx
cdef short num_valid_target = 0
cdef short attr_id = entity_attr["id"]
cdef short attr_time_alive = entity_attr["time_alive"]
cdef short attr_npc_type = entity_attr["npc_type"]
cdef short attr_row = entity_attr["row"]
cdef short attr_col = entity_attr["col"]
for idx in range(len(entities)):
# skip empty row
if entities[idx, attr_id] == 0:
continue
# out of range
if abs(entities[idx, attr_row] - my_info["row"]) > my_info["attack_range"] or \
abs(entities[idx, attr_col] - my_info["col"]) > my_info["attack_range"]:
continue
# cannot attack during immunity
if entities[idx, attr_id] > 0 and \
entities[idx, attr_time_alive] < my_info["immunity"]:
continue
# cannot attack self
if entities[idx, attr_id] == my_info["agent_id"]:
continue
# npc_type must be 0, 1, 2, 3
if entities[idx, attr_npc_type] < 0: # immortal (-1)
continue
mask[idx] = 1
num_valid_target += 1
# cython: wraparound need to be True
# if any valid target, set the no-op to 0
mask[-1] = 0 if num_valid_target > 0 else 1
def parse_array(short[:] data, dict attr_name_to_col):
cdef short col
cdef str attr
cdef dict result = {}
for attr, col in attr_name_to_col.items():
result[attr] = data[col]
return SimpleNamespace(**result)
================================================
FILE: nmmo/lib/event_code.py
================================================
class EventCode:
# Move
EAT_FOOD
gitextract_fsthiw8v/
├── .gitattributes
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ ├── documentation.md
│ │ ├── enhancement.md
│ │ └── feature_request.md
│ └── workflows/
│ └── pylint-test.yml
├── .gitignore
├── .pylintrc
├── LICENSE
├── MANIFEST.in
├── README.md
├── nmmo/
│ ├── __init__.py
│ ├── core/
│ │ ├── __init__.py
│ │ ├── action.py
│ │ ├── agent.py
│ │ ├── config.py
│ │ ├── env.py
│ │ ├── game_api.py
│ │ ├── map.py
│ │ ├── observation.py
│ │ ├── realm.py
│ │ ├── terrain.py
│ │ └── tile.py
│ ├── datastore/
│ │ ├── __init__.py
│ │ ├── datastore.py
│ │ ├── id_allocator.py
│ │ ├── numpy_datastore.py
│ │ └── serialized.py
│ ├── entity/
│ │ ├── __init__.py
│ │ ├── entity.py
│ │ ├── entity_manager.py
│ │ ├── npc.py
│ │ ├── npc_manager.py
│ │ └── player.py
│ ├── lib/
│ │ ├── __init__.py
│ │ ├── astar.py
│ │ ├── colors.py
│ │ ├── cython_helper.pyx
│ │ ├── event_code.py
│ │ ├── event_log.py
│ │ ├── material.py
│ │ ├── seeding.py
│ │ ├── spawn.py
│ │ ├── team_helper.py
│ │ ├── utils.py
│ │ └── vec_noise.py
│ ├── minigames/
│ │ ├── __init__.py
│ │ ├── center_race.py
│ │ ├── comm_together.py
│ │ ├── king_hill.py
│ │ ├── radio_raid.py
│ │ └── sandwich.py
│ ├── render/
│ │ ├── __init__.py
│ │ ├── overlay.py
│ │ ├── render_client.py
│ │ └── render_utils.py
│ ├── systems/
│ │ ├── __init__.py
│ │ ├── combat.py
│ │ ├── droptable.py
│ │ ├── exchange.py
│ │ ├── inventory.py
│ │ ├── item.py
│ │ └── skill.py
│ ├── task/
│ │ ├── __init__.py
│ │ ├── base_predicates.py
│ │ ├── game_state.py
│ │ ├── group.py
│ │ ├── predicate_api.py
│ │ ├── task_api.py
│ │ └── task_spec.py
│ └── version.py
├── pyproject.toml
├── scripted/
│ ├── __init__.py
│ ├── attack.py
│ ├── baselines.py
│ └── move.py
├── setup.py
├── tests/
│ ├── __init__.py
│ ├── action/
│ │ ├── test_ammo_use.py
│ │ ├── test_destroy_give_gold.py
│ │ ├── test_monkey_action.py
│ │ └── test_sell_buy.py
│ ├── conftest.py
│ ├── core/
│ │ ├── test_config.py
│ │ ├── test_cython_masks.py
│ │ ├── test_entity.py
│ │ ├── test_env.py
│ │ ├── test_game_api.py
│ │ ├── test_gym_obs_spaces.py
│ │ ├── test_map_generation.py
│ │ ├── test_observation_tile.py
│ │ ├── test_tile_property.py
│ │ └── test_tile_seize.py
│ ├── datastore/
│ │ ├── test_datastore.py
│ │ ├── test_id_allocator.py
│ │ ├── test_numpy_datastore.py
│ │ └── test_serialized.py
│ ├── render/
│ │ ├── test_load_replay.py
│ │ └── test_render_save.py
│ ├── systems/
│ │ ├── test_exchange.py
│ │ ├── test_item.py
│ │ └── test_skill_level.py
│ ├── task/
│ │ ├── sample_curriculum.pkl
│ │ ├── test_demo_task_creation.py
│ │ ├── test_manual_curriculum.py
│ │ ├── test_predicates.py
│ │ ├── test_sample_task_from_file.py
│ │ ├── test_task_api.py
│ │ └── test_task_system_perf.py
│ ├── test_death_fog.py
│ ├── test_determinism.py
│ ├── test_eventlog.py
│ ├── test_memory_usage.py
│ ├── test_mini_games.py
│ ├── test_performance.py
│ ├── test_pettingzoo.py
│ ├── test_rollout.py
│ └── testhelpers.py
└── utils/
├── git-pr.sh
├── pre-git-check.sh
└── run-perf-tests.sh
SYMBOL INDEX (1447 symbols across 89 files)
FILE: nmmo/core/action.py
class NodeType (line 12) | class NodeType(Enum):
class Node (line 22) | class Node(metaclass=utils.IterableNameComparable):
method init (line 24) | def init(cls, config):
method edges (line 29) | def edges():
method priority (line 34) | def priority():
method type (line 38) | def type():
method leaf (line 42) | def leaf():
method N (line 46) | def N(cls, config):
method deserialize (line 49) | def deserialize(realm, entity, index: int, obs: Observation):
class Fixed (line 52) | class Fixed:
class Action (line 56) | class Action(Node):
method init (line 61) | def init(cls, config):
method hook (line 70) | def hook(config):
method n (line 87) | def n():
method edges (line 92) | def edges(cls, config):
class Move (line 105) | class Move(Node):
method call (line 108) | def call(realm, entity, direction):
method edges (line 152) | def edges():
method leaf (line 156) | def leaf():
method enabled (line 159) | def enabled(config):
class Direction (line 162) | class Direction(Node):
method edges (line 166) | def edges():
method deserialize (line 169) | def deserialize(realm, entity, index: int, obs):
function deserialize_fixed_arg (line 173) | def deserialize_fixed_arg(arg, index):
class North (line 185) | class North(Node):
class South (line 188) | class South(Node):
class East (line 191) | class East(Node):
class West (line 194) | class West(Node):
class Stay (line 197) | class Stay(Node):
class Attack (line 200) | class Attack(Node):
method n (line 204) | def n():
method edges (line 208) | def edges():
method leaf (line 212) | def leaf():
method enabled (line 215) | def enabled(config):
method in_range (line 218) | def in_range(entity, stim, config, N):
method call (line 231) | def call(realm, entity, style, target):
class Style (line 272) | class Style(Node):
method edges (line 275) | def edges():
method deserialize (line 278) | def deserialize(realm, entity, index: int, obs):
class Target (line 281) | class Target(Node):
method N (line 285) | def N(cls, config):
method deserialize (line 288) | def deserialize(realm, entity, index: int, obs: Observation):
class Melee (line 293) | class Melee(Node):
method attack_range (line 297) | def attack_range(config):
method skill (line 300) | def skill(entity):
class Range (line 303) | class Range(Node):
method attack_range (line 307) | def attack_range(config):
method skill (line 310) | def skill(entity):
class Mage (line 313) | class Mage(Node):
method attack_range (line 317) | def attack_range(config):
method skill (line 320) | def skill(entity):
class InventoryItem (line 323) | class InventoryItem(Node):
method N (line 327) | def N(cls, config):
method deserialize (line 330) | def deserialize(realm, entity, index: int, obs: Observation):
class Use (line 335) | class Use(Node):
method edges (line 339) | def edges():
method enabled (line 342) | def enabled(config):
method call (line 345) | def call(realm, entity, item):
class Destroy (line 368) | class Destroy(Node):
method edges (line 372) | def edges():
method enabled (line 375) | def enabled(config):
method call (line 378) | def call(realm, entity, item):
class Give (line 402) | class Give(Node):
method edges (line 406) | def edges():
method enabled (line 409) | def enabled(config):
method call (line 412) | def call(realm, entity, item, target):
class GiveGold (line 460) | class GiveGold(Node):
method edges (line 464) | def edges():
method enabled (line 468) | def enabled(config):
method call (line 471) | def call(realm, entity, amount, target):
class MarketItem (line 508) | class MarketItem(Node):
method N (line 512) | def N(cls, config):
method deserialize (line 515) | def deserialize(realm, entity, index: int, obs: Observation):
class Buy (line 520) | class Buy(Node):
method edges (line 525) | def edges():
method enabled (line 528) | def enabled(config):
method call (line 531) | def call(realm, entity, item):
class Sell (line 564) | class Sell(Node):
method edges (line 569) | def edges():
method enabled (line 572) | def enabled(config):
method call (line 575) | def call(realm, entity, item, price):
function init_discrete (line 604) | def init_discrete(values):
class Price (line 613) | class Price(Node):
method init (line 617) | def init(cls, config):
method index (line 623) | def index(cls, price):
method edges (line 631) | def edges():
method deserialize (line 634) | def deserialize(realm, entity, index: int, obs):
class Token (line 637) | class Token(Node):
method init (line 641) | def init(cls, config):
method edges (line 645) | def edges():
method deserialize (line 648) | def deserialize(realm, entity, index: int, obs):
class Comm (line 651) | class Comm(Node):
method edges (line 656) | def edges():
method enabled (line 659) | def enabled(config):
method call (line 662) | def call(realm, entity, token):
class BecomeSkynet (line 669) | class BecomeSkynet:
FILE: nmmo/core/agent.py
class Agent (line 1) | class Agent:
method __init__ (line 4) | def __init__(self, config, idx):
method __call__ (line 15) | def __call__(self, obs):
method set_rng (line 22) | def set_rng(self, np_random):
class Scripted (line 30) | class Scripted(Agent):
FILE: nmmo/core/config.py
class Template (line 27) | class Template(metaclass=utils.StaticIterable):
method __init__ (line 28) | def __init__(self):
method override (line 38) | def override(self, **kwargs):
method set (line 44) | def set(self, k, v):
method print (line 54) | def print(self):
method items (line 63) | def items(self):
method __iter__ (line 66) | def __iter__(self):
method keys (line 70) | def keys(self):
method values (line 73) | def values(self):
function validate (line 76) | def validate(config):
class Config (line 97) | class Config(Template):
method __init__ (line 101) | def __init__(self):
method original (line 121) | def original(self):
method reset (line 124) | def reset(self):
method set (line 129) | def set(self, k, v):
method set_for_episode (line 133) | def set_for_episode(self, k, v):
method enabled_systems (line 147) | def enabled_systems(self):
method system_states (line 153) | def system_states(self):
method are_systems_enabled (line 158) | def are_systems_enabled(self, systems): # systems is a list of strings
method toggle_systems (line 162) | def toggle_systems(self, target_systems): # systems is a list of strings
method PLAYER_POLICIES (line 179) | def PLAYER_POLICIES(self):
method POSSIBLE_AGENTS (line 187) | def POSSIBLE_AGENTS(self):
method PLAYER_VISION_DIAMETER (line 245) | def PLAYER_VISION_DIAMETER(self):
method MAP_N_OBS (line 280) | def MAP_N_OBS(self):
method MAP_BORDER (line 291) | def MAP_BORDER(self):
class Terrain (line 337) | class Terrain:
class Resource (line 384) | class Resource:
function original_combat_damage_formula (line 427) | def original_combat_damage_formula(self, offense, defense, multiplier, m...
function alt_combat_damage_formula (line 431) | def alt_combat_damage_formula(self, offense, defense, multiplier, minimu...
class Combat (line 435) | class Combat:
function default_exp_threshold (line 480) | def default_exp_threshold(base_exp, max_level):
class Progression (line 486) | class Progression:
class NPC (line 535) | class NPC:
class Item (line 584) | class Item:
method INVENTORY_N_OBS (line 600) | def INVENTORY_N_OBS(self):
class Equipment (line 605) | class Equipment:
class Profession (line 639) | class Profession:
method PROFESSION_CONSUMABLE_RESTORE (line 675) | def PROFESSION_CONSUMABLE_RESTORE(self, level):
class Exchange (line 680) | class Exchange:
class Communication (line 701) | class Communication:
class AllGameSystems (line 714) | class AllGameSystems(
class Small (line 722) | class Small(Config):
class Medium (line 745) | class Medium(Config):
class Large (line 766) | class Large(Config):
class Default (line 787) | class Default(Medium, AllGameSystems):
FILE: nmmo/core/env.py
class Env (line 23) | class Env(ParallelEnv):
method __init__ (line 27) | def __init__(self,
method _obs_space (line 85) | def _obs_space(self):
method observation_space (line 129) | def observation_space(self, agent: AgentID):
method _atn_space (line 142) | def _atn_space(self):
method _str_atn_map (line 154) | def _str_atn_map(self):
method action_space (line 165) | def action_space(self, agent: AgentID):
method reset (line 179) | def reset(self, seed=None, options=None, # PettingZoo API args
method _load_map_file (line 280) | def _load_map_file(self, map_id: int=None):
method _map_task_to_agent (line 293) | def _map_task_to_agent(self):
method step (line 313) | def step(self, actions: Dict[int, Dict[str, Dict[str, Any]]]):
method dead_this_tick (line 375) | def dead_this_tick(self):
method _validate_actions (line 378) | def _validate_actions(self, actions: Dict[int, Dict[str, Dict[str, Any...
method _compute_scripted_agent_actions (line 423) | def _compute_scripted_agent_actions(self, actions: Dict[int, Dict[str,...
method _compute_observations (line 438) | def _compute_observations(self):
method _update_comm_obs (line 461) | def _update_comm_obs(self):
method _compute_rewards (line 479) | def _compute_rewards(self):
method render (line 520) | def render(self, mode='human'):
method agents (line 524) | def agents(self) -> List[AgentID]:
method close (line 528) | def close(self):
method seed (line 531) | def seed(self, seed=None):
method state (line 543) | def state(self) -> np.ndarray:
FILE: nmmo/core/game_api.py
class Game (line 14) | class Game(ABC):
method __init__ (line 17) | def __init__(self, env, sampling_weight=None):
method is_compatible (line 32) | def is_compatible(self):
method name (line 37) | def name(self):
method winners (line 41) | def winners(self):
method winning_score (line 45) | def winning_score(self):
method reset (line 51) | def reset(self, np_random, map_dict, tasks=None):
method _set_config (line 68) | def _set_config(self): # pylint: disable=unused-argument
method _set_realm (line 72) | def _set_realm(self, map_dict):
method _post_setup (line 76) | def _post_setup(self):
method _reset_stats (line 79) | def _reset_stats(self):
method _define_tasks (line 88) | def _define_tasks(self):
method set_next_tasks (line 94) | def set_next_tasks(self, tasks):
method update (line 98) | def update(self, terminated, dead_players, dead_npcs):
method _process_dead_players (line 110) | def _process_dead_players(self, terminated, dead_players):
method _process_dead_npcs (line 118) | def _process_dead_npcs(self, dead_npcs):
method _check_winners (line 126) | def _check_winners(self, terminated):
method is_over (line 139) | def is_over(self):
method get_episode_stats (line 143) | def get_episode_stats(self):
method _who_completed_task (line 158) | def _who_completed_task(self):
class DefaultGame (line 167) | class DefaultGame(Game):
method is_compatible (line 171) | def is_compatible(self):
method _define_tasks (line 174) | def _define_tasks(self):
class AgentTraining (line 177) | class AgentTraining(Game):
method winning_score (line 182) | def winning_score(self):
method is_compatible (line 185) | def is_compatible(self):
method _define_tasks (line 194) | def _define_tasks(self):
class TeamGameTemplate (line 206) | class TeamGameTemplate(Game):
method is_compatible (line 210) | def is_compatible(self):
method _set_realm (line 223) | def _set_realm(self, map_dict):
method _post_setup (line 230) | def _post_setup(self):
method teams (line 234) | def teams(self):
method _attach_team_tag (line 237) | def _attach_team_tag(self):
method _get_cand_team_tasks (line 247) | def _get_cand_team_tasks(self, num_tasks, tags=None):
class TeamTraining (line 261) | class TeamTraining(TeamGameTemplate):
method _define_tasks (line 265) | def _define_tasks(self):
function team_survival_task (line 269) | def team_survival_task(num_tick, embedding=None):
class TeamBattle (line 276) | class TeamBattle(TeamGameTemplate):
method __init__ (line 280) | def __init__(self, env, sampling_weight=None):
method is_compatible (line 285) | def is_compatible(self):
method _define_tasks (line 293) | def _define_tasks(self):
method _check_winners (line 300) | def _check_winners(self, terminated):
method _check_remaining_teams (line 311) | def _check_remaining_teams(self):
class ProtectTheKing (line 319) | class ProtectTheKing(TeamBattle):
method __init__ (line 320) | def __init__(self, env, sampling_weight=None):
method _define_tasks (line 326) | def _define_tasks(self):
method update (line 338) | def update(self, terminated, dead_players, dead_npcs):
FILE: nmmo/core/map.py
class Map (line 15) | class Map:
method __init__ (line 20) | def __init__(self, config, realm, np_random):
method packet (line 45) | def packet(self):
method repr (line 53) | def repr(self):
method reset (line 59) | def reset(self, map_dict, np_random, seize_targets=None):
method _process_map (line 105) | def _process_map(self, map_dict, np_random):
method _mark_tile (line 129) | def _mark_tile(map_np_array, row, col, dist=2):
method step (line 133) | def step(self):
method harvest (line 143) | def harvest(self, r, c, deplete=True):
method is_valid_pos (line 149) | def is_valid_pos(self, row, col):
method make_spawnable (line 153) | def make_spawnable(self, row, col, radius=2):
method seize_status (line 169) | def seize_status(self):
FILE: nmmo/core/observation.py
class BasicObs (line 19) | class BasicObs:
method __init__ (line 20) | def __init__(self, id_col, obs_dim):
method reset (line 26) | def reset(self):
method update (line 30) | def update(self, values):
method len (line 35) | def len(self):
method id (line 38) | def id(self, i):
method index (line 41) | def index(self, val):
class InventoryObs (line 44) | class InventoryObs(BasicObs):
method __init__ (line 45) | def __init__(self, id_col, obs_dim):
method update (line 50) | def update(self, values):
method sig (line 55) | def sig(self, item: item_system.Item, level: int):
class GymObs (line 59) | class GymObs:
method __init__ (line 62) | def __init__(self, config, agent_id):
method reset (line 67) | def reset(self, task_embedding=None):
method clear (line 71) | def clear(self, tick=None):
method _make_empty_obs (line 83) | def _make_empty_obs(self):
method set_arr_values (line 105) | def set_arr_values(self, key, values):
method export (line 109) | def export(self):
class ActionTargets (line 112) | class ActionTargets:
method __init__ (line 116) | def __init__(self, config):
method _get_keys_to_clear (line 126) | def _get_keys_to_clear(self):
method reset (line 138) | def reset(self):
method clear (line 144) | def clear(self, reset=False):
method _make_empty_targets (line 154) | def _make_empty_targets(self):
class Observation (line 182) | class Observation:
method __init__ (line 183) | def __init__(self, config, agent_id: int) -> None:
method reset (line 214) | def reset(self, habitable_tiles, task_embedding=None):
method return_dummy_obs (line 233) | def return_dummy_obs(self):
method set_agent_dead (line 236) | def set_agent_dead(self):
method update (line 239) | def update(self, tick, visible_tiles, visible_entities,
method tile (line 271) | def tile(self, r_delta, c_delta):
method entity (line 288) | def entity(self, entity_id):
method clear_cache (line 294) | def clear_cache(self):
method to_gym (line 299) | def to_gym(self):
method _make_action_targets (line 320) | def _make_action_targets(self):
method _make_move_mask (line 338) | def _make_move_mask(self, move_mask, use_cython=None):
method _make_attack_mask (line 347) | def _make_attack_mask(self, attack_mask, use_cython=None):
method _make_use_mask (line 389) | def _make_use_mask(self, use_mask):
method _item_skill (line 408) | def _item_skill(self):
method _make_destroy_item_mask (line 434) | def _make_destroy_item_mask(self, destroy_mask):
method _make_give_mask (line 443) | def _make_give_mask(self, give_mask):
method _make_sell_mask (line 463) | def _make_sell_mask(self, sell_mask):
method _make_give_gold_mask (line 473) | def _make_give_gold_mask(self, give_mask):
method _make_buy_mask (line 491) | def _make_buy_mask(self, buy_mask):
method _existing_ammo_listings (line 510) | def _existing_ammo_listings(self):
FILE: nmmo/core/realm.py
function prioritized (line 19) | def prioritized(entities: Dict, merged: Dict):
class Realm (line 27) | class Realm:
method __init__ (line 30) | def __init__(self, config, np_random):
method reset (line 68) | def reset(self, np_random, map_dict,
method packet (line 104) | def packet(self):
method num_players (line 117) | def num_players(self):
method seize_status (line 122) | def seize_status(self):
method entity (line 125) | def entity(self, ent_id):
method entity_or_none (line 130) | def entity_or_none(self, ent_id):
method step (line 140) | def step(self, actions):
method update_fog_map (line 197) | def update_fog_map(self, reset=False):
method record_replay (line 224) | def record_replay(self, replay_helper: ReplayHelper) -> ReplayHelper:
FILE: nmmo/core/terrain.py
function sharp (line 11) | def sharp(noise):
class Save (line 15) | class Save:
method render (line 18) | def render(mats, lookup, path):
method fractal (line 25) | def fractal(terrain, path):
method as_numpy (line 31) | def as_numpy(mats, path):
class Terrain (line 38) | class Terrain:
method generate_terrain (line 41) | def generate_terrain(config, map_id, interpolaters):
function fractal_to_material (line 123) | def fractal_to_material(config, fractal, all_grass=False):
function process_map_border (line 144) | def process_map_border(config, matl_map, l1=None):
function place_fish (line 158) | def place_fish(tiles, mmin, mmax, np_random, num_fish):
function uniform (line 184) | def uniform(config, tiles, mat, mmin, mmax, np_random):
function cluster (line 193) | def cluster(config, tiles, mat, mmin, mmax, np_random):
function spawn_profession_resources (line 215) | def spawn_profession_resources(config, tiles, np_random=None):
function try_add_tile (line 232) | def try_add_tile(map_tiles, row, col, tile_to_add):
function scatter_extra_resources (line 238) | def scatter_extra_resources(config, tiles, np_random=None,
class MapGenerator (line 258) | class MapGenerator:
method __init__ (line 260) | def __init__(self, config):
method load_textures (line 265) | def load_textures(self):
method generate_all_maps (line 277) | def generate_all_maps(self, seed=None):
method generate_map (line 315) | def generate_map(self, idx, np_random=None):
FILE: nmmo/core/tile.py
class Tile (line 30) | class Tile(TileState):
method __init__ (line 31) | def __init__(self, realm, r, c, np_random):
method occupied (line 47) | def occupied(self):
method repr (line 55) | def repr(self):
method pos (line 59) | def pos(self):
method habitable (line 63) | def habitable(self):
method impassible (line 67) | def impassible(self):
method void (line 71) | def void(self):
method tex (line 75) | def tex(self):
method reset (line 78) | def reset(self, mat, config, np_random):
method set_depleted (line 85) | def set_depleted(self):
method _respawn (line 90) | def _respawn(self):
method add_entity (line 95) | def add_entity(self, ent):
method remove_entity (line 99) | def remove_entity(self, ent_id):
method step (line 103) | def step(self):
method harvest (line 109) | def harvest(self, deplete):
method update_seize (line 116) | def update_seize(self):
FILE: nmmo/datastore/datastore.py
class DataTable (line 25) | class DataTable:
method __init__ (line 26) | def __init__(self, num_columns: int):
method reset (line 30) | def reset(self):
method update (line 33) | def update(self, row_id: int, col: int, value):
method get (line 36) | def get(self, ids: List[id]):
method where_in (line 39) | def where_in(self, col: int, values: List):
method where_eq (line 42) | def where_eq(self, col: str, value):
method where_neq (line 45) | def where_neq(self, col: str, value):
method window (line 48) | def window(self, row_idx: int, col_idx: int, row: int, col: int, radiu...
method remove_row (line 51) | def remove_row(self, row_id: int):
method add_row (line 54) | def add_row(self) -> int:
method is_empty (line 57) | def is_empty(self) -> bool:
class DatastoreRecord (line 60) | class DatastoreRecord:
method __init__ (line 61) | def __init__(self, datastore, table: DataTable, row_id: int) -> None:
method update (line 66) | def update(self, col: int, value):
method get (line 69) | def get(self, col: int):
method delete (line 72) | def delete(self):
class Datastore (line 75) | class Datastore:
method __init__ (line 76) | def __init__(self) -> None:
method register_object_type (line 79) | def register_object_type(self, object_type: str, num_colums: int):
method create_record (line 83) | def create_record(self, object_type: str) -> DatastoreRecord:
method table (line 88) | def table(self, object_type: str) -> DataTable:
method _create_table (line 91) | def _create_table(self, num_columns: int) -> DataTable:
FILE: nmmo/datastore/id_allocator.py
class IdAllocator (line 3) | class IdAllocator:
method __init__ (line 4) | def __init__(self, max_id):
method full (line 10) | def full(self):
method remove (line 13) | def remove(self, row_id):
method allocate (line 16) | def allocate(self):
method expand (line 19) | def expand(self, max_id):
FILE: nmmo/datastore/numpy_datastore.py
class NumpyTable (line 8) | class NumpyTable(DataTable):
method __init__ (line 9) | def __init__(self, num_columns: int, initial_size: int, dtype=np.int16):
method reset (line 17) | def reset(self):
method update (line 23) | def update(self, row_id: int, col: int, value):
method get (line 26) | def get(self, ids: List[int]):
method where_eq (line 29) | def where_eq(self, col: int, value):
method where_neq (line 32) | def where_neq(self, col: int, value):
method where_gt (line 35) | def where_gt(self, col: int, value):
method where_in (line 38) | def where_in(self, col: int, values: List):
method window (line 41) | def window(self, row_idx: int, col_idx: int, row: int, col: int, radiu...
method add_row (line 47) | def add_row(self) -> int:
method remove_row (line 53) | def remove_row(self, row_id: int) -> int:
method _expand (line 57) | def _expand(self, max_rows: int):
method is_empty (line 65) | def is_empty(self) -> bool:
class NumpyDatastore (line 71) | class NumpyDatastore(Datastore):
method _create_table (line 72) | def _create_table(self, num_columns: int) -> DataTable:
FILE: nmmo/datastore/serialized.py
class SerializedAttribute (line 30) | class SerializedAttribute():
method __init__ (line 31) | def __init__(self,
method val (line 43) | def val(self):
method update (line 46) | def update(self, value):
method min (line 55) | def min(self):
method max (line 59) | def max(self):
method increment (line 62) | def increment(self, val=1, max_v=math.inf):
method decrement (line 66) | def decrement(self, val=1, min_v=-math.inf):
method empty (line 71) | def empty(self):
method __eq__ (line 74) | def __eq__(self, other):
method __ne__ (line 77) | def __ne__(self, other):
method __lt__ (line 80) | def __lt__(self, other):
method __le__ (line 83) | def __le__(self, other):
method __gt__ (line 86) | def __gt__(self, other):
method __ge__ (line 89) | def __ge__(self, other):
class SerializedState (line 92) | class SerializedState():
method subclass (line 94) | def subclass(name: str, attributes: List[str]):
FILE: nmmo/entity/entity.py
class Resources (line 124) | class Resources:
method __init__ (line 125) | def __init__(self, ent, config):
method update (line 138) | def update(self, immortal=False):
method packet (line 168) | def packet(self):
class Status (line 178) | class Status:
method __init__ (line 179) | def __init__(self, ent):
method update (line 182) | def update(self):
method packet (line 186) | def packet(self):
method frozen (line 192) | def frozen(self):
class History (line 197) | class History:
method __init__ (line 198) | def __init__(self, ent):
method update (line 214) | def update(self, entity, actions):
method packet (line 224) | def packet(self):
class Entity (line 255) | class Entity(EntityState):
method __init__ (line 256) | def __init__(self, realm, pos, entity_id, name):
method packet (line 293) | def packet(self):
method update (line 307) | def update(self, realm, actions):
method receive_damage (line 322) | def receive_damage(self, source, dmg):
method apply_damage (line 351) | def apply_damage(self, dmg, style):
method pos (line 355) | def pos(self):
method set_pos (line 360) | def set_pos(self, row, col):
method alive (line 366) | def alive(self):
method immortal (line 370) | def immortal(self):
method is_player (line 374) | def is_player(self) -> bool:
method is_npc (line 378) | def is_npc(self) -> bool:
method is_recon (line 382) | def is_recon(self):
method attack_level (line 386) | def attack_level(self) -> int:
method in_combat (line 393) | def in_combat(self) -> bool:
FILE: nmmo/entity/entity_manager.py
class EntityGroup (line 9) | class EntityGroup(Mapping):
method __init__ (line 10) | def __init__(self, realm, np_random):
method __len__ (line 21) | def __len__(self):
method __contains__ (line 24) | def __contains__(self, e):
method __getitem__ (line 27) | def __getitem__(self, key) -> Entity:
method __iter__ (line 30) | def __iter__(self) -> Entity:
method items (line 33) | def items(self):
method corporeal (line 37) | def corporeal(self):
method packet (line 41) | def packet(self):
method reset (line 44) | def reset(self, np_random, delete_dead_entity=True):
method spawn_entity (line 57) | def spawn_entity(self, entity):
method cull_entity (line 62) | def cull_entity(self, entity):
method cull (line 74) | def cull(self):
method update (line 83) | def update(self, actions):
class PlayerManager (line 96) | class PlayerManager(EntityGroup):
method spawn (line 97) | def spawn(self, agent_loader: spawn.SequentialLoader = None):
FILE: nmmo/entity/npc.py
function get_habitable_dir (line 20) | def get_habitable_dir(ent):
function meander_toward (line 30) | def meander_toward(ent, goal, dist_crit=10, toward_weight=3):
function move_action (line 60) | def move_action(direction):
class Equipment (line 64) | class Equipment:
method __init__ (line 65) | def __init__(self, total,
method total (line 79) | def total(self, getter):
method packet (line 85) | def packet(self):
class NPC (line 98) | class NPC(entity.Entity):
method __init__ (line 99) | def __init__(self, realm, pos, iden, name, npc_type):
method is_npc (line 110) | def is_npc(self) -> bool:
method update (line 113) | def update(self, realm, actions):
method can_see (line 122) | def can_see(self, target):
method _move_toward (line 128) | def _move_toward(self, goal):
method _meander (line 132) | def _meander(self):
method can_attack (line 135) | def can_attack(self, target):
method _has_target (line 143) | def _has_target(self, search=False):
method _add_attack_action (line 153) | def _add_attack_action(self, actions, target):
method _charge_toward (line 156) | def _charge_toward(self, target):
method receive_damage (line 163) | def receive_damage(self, source, dmg):
method default_spawn (line 188) | def default_spawn(realm, pos, iden, np_random, danger=None):
method packet (line 262) | def packet(self):
class Passive (line 269) | class Passive(NPC):
method __init__ (line 270) | def __init__(self, realm, pos, iden, name=None):
method decide (line 273) | def decide(self):
class PassiveAggressive (line 277) | class PassiveAggressive(NPC):
method __init__ (line 278) | def __init__(self, realm, pos, iden, name=None):
method decide (line 281) | def decide(self):
class Aggressive (line 286) | class Aggressive(NPC):
method __init__ (line 287) | def __init__(self, realm, pos, iden, name=None):
method decide (line 290) | def decide(self):
class Soldier (line 295) | class Soldier(NPC):
method __init__ (line 296) | def __init__(self, realm, pos, iden, name, order):
method _process_order (line 302) | def _process_order(self, order):
method _is_order_done (line 311) | def _is_order_done(self, radius=5):
method decide (line 317) | def decide(self):
method _decide_move_action (line 328) | def _decide_move_action(self):
method _decide_attack_action (line 339) | def _decide_attack_action(self, actions):
FILE: nmmo/entity/npc_manager.py
class NPCManager (line 9) | class NPCManager(EntityGroup):
method __init__ (line 10) | def __init__(self, realm, np_random):
method reset (line 15) | def reset(self, np_random):
method actions (line 20) | def actions(self):
method default_spawn (line 23) | def default_spawn(self):
method spawn_npc (line 46) | def spawn_npc(self, r, c, danger=None, name=None, order=None,
method area_spawn (line 72) | def area_spawn(self, r_min, r_max, c_min, c_max, num_spawn,
method edge_spawn (line 82) | def edge_spawn(self, num_spawn, npc_init_fn: Callable):
FILE: nmmo/entity/player.py
class Player (line 7) | class Player(entity.Entity):
method __init__ (line 8) | def __init__(self, realm, pos, agent, resilient=False):
method serial (line 41) | def serial(self):
method is_player (line 45) | def is_player(self) -> bool:
method level (line 49) | def level(self) -> int:
method _set_immortal (line 55) | def _set_immortal(self, value=True, duration=None):
method make_recon (line 65) | def make_recon(self, new_pos=None):
method apply_damage (line 76) | def apply_damage(self, dmg, style):
method receive_damage (line 81) | def receive_damage(self, source, dmg):
method equipment (line 120) | def equipment(self):
method packet (line 123) | def packet(self):
method update (line 137) | def update(self, realm, actions):
method resurrect (line 165) | def resurrect(self, health_prop=0.5, freeze_duration=10, edge_spawn=Tr...
FILE: nmmo/lib/astar.py
function l1 (line 7) | def l1(start, goal):
function adjacentPos (line 12) | def adjacentPos(pos):
function aStar (line 16) | def aStar(realm_map, start, goal, cutoff = CUTOFF):
FILE: nmmo/lib/colors.py
function rgb (line 10) | def rgb(h):
function rgbNorm (line 14) | def rgbNorm(h):
function makeColor (line 18) | def makeColor(idx, h=1, s=1, v=1):
class Color (line 24) | class Color:
method __init__ (line 25) | def __init__(self, name, hexVal):
method packet (line 32) | def packet(self):
class Color256 (line 35) | class Color256:
method make256 (line 36) | def make256():
class Color16 (line 45) | class Color16:
method make (line 46) | def make():
class Tier (line 54) | class Tier:
class Swatch (line 63) | class Swatch:
method colors (line 64) | def colors():
method rand (line 68) | def rand():
class Neon (line 75) | class Neon(Swatch):
method colors (line 104) | def colors():
class Solid (line 111) | class Solid(Swatch):
method colors (line 126) | def colors():
class Palette (line 133) | class Palette:
method __init__ (line 134) | def __init__(self, initial_swatch=Neon):
method color (line 139) | def color(self, idx):
FILE: nmmo/lib/event_code.py
class EventCode (line 1) | class EventCode:
FILE: nmmo/lib/event_log.py
class EventLogger (line 53) | class EventLogger(EventCode):
method __init__ (line 54) | def __init__(self, realm):
method reset (line 73) | def reset(self):
method _create_event (line 77) | def _create_event(self, entity: Entity, event_code: int):
method record (line 87) | def record(self, event_code: int, entity: Entity, **kwargs):
method update (line 188) | def update(self):
method get_data (line 194) | def get_data(self, event_code=None, agents: List[int]=None, tick: int=...
method get_stat (line 215) | def get_stat(self):
function extract_event_key (line 238) | def extract_event_key(event_row):
FILE: nmmo/lib/material.py
class Material (line 4) | class Material:
method __init__ (line 11) | def __init__(self, config):
method __eq__ (line 14) | def __eq__(self, mtl):
method __equals__ (line 17) | def __equals__(self, mtl):
method harvest (line 20) | def harvest(self):
class Void (line 23) | class Void(Material):
class Water (line 27) | class Water(Material):
method __init__ (line 33) | def __init__(self, config):
class Grass (line 37) | class Grass(Material):
class Scrub (line 41) | class Scrub(Material):
class Foilage (line 45) | class Foilage(Material):
method __init__ (line 52) | def __init__(self, config):
class Stone (line 57) | class Stone(Material):
class Slag (line 61) | class Slag(Material):
class Ore (line 65) | class Ore(Material):
method __init__ (line 72) | def __init__(self, config):
class Stump (line 88) | class Stump(Material):
class Tree (line 92) | class Tree(Material):
method __init__ (line 99) | def __init__(self, config):
class Fragment (line 111) | class Fragment(Material):
class Crystal (line 115) | class Crystal(Material):
method __init__ (line 122) | def __init__(self, config):
class Weeds (line 134) | class Weeds(Material):
class Herb (line 138) | class Herb(Material):
method __init__ (line 148) | def __init__(self, config):
class Ocean (line 153) | class Ocean(Material):
class Fish (line 157) | class Fish(Material):
method __init__ (line 167) | def __init__(self, config):
class Meta (line 174) | class Meta(type):
method __init__ (line 175) | def __init__(self, name, bases, dict):
method __iter__ (line 178) | def __iter__(self):
method __contains__ (line 181) | def __contains__(self, mtl):
class All (line 188) | class All(metaclass=Meta):
class Impassible (line 195) | class Impassible(metaclass=Meta):
class Habitable (line 199) | class Habitable(metaclass=Meta):
class Harvestable (line 203) | class Harvestable(metaclass=Meta):
FILE: nmmo/lib/seeding.py
class RandomNumberGenerator (line 7) | class RandomNumberGenerator(np.random.Generator):
method __init__ (line 8) | def __init__(self, bit_generator):
method get_direction (line 16) | def get_direction(self):
function np_random (line 20) | def np_random(seed: Optional[int] = None) -> Tuple[np.random.Generator, ...
FILE: nmmo/lib/spawn.py
class SequentialLoader (line 3) | class SequentialLoader:
method __init__ (line 5) | def __init__(self, config, np_random,
method __iter__ (line 18) | def __iter__(self):
method __next__ (line 21) | def __next__(self):
method get_spawn_position (line 26) | def get_spawn_position(self, agent_id):
function get_random_coord (line 30) | def get_random_coord(config, np_random, edge=True):
function get_edge_tiles (line 56) | def get_edge_tiles(config, np_random=None, shuffle=False):
FILE: nmmo/lib/team_helper.py
function make_teams (line 6) | def make_teams(config, num_teams):
class TeamHelper (line 14) | class TeamHelper:
method __init__ (line 15) | def __init__(self, teams: Dict[Any, List[int]], np_random=None):
method agent_position (line 34) | def agent_position(self, agent_id: int) -> int:
method agent_id (line 37) | def agent_id(self, team_id: Any, position: int) -> int:
method is_agent_in_team (line 40) | def is_agent_in_team(self, agent_id:int , team_id: Any) -> bool:
method get_team_idx (line 43) | def get_team_idx(self, agent_id:int) -> int:
method get_target_agent (line 47) | def get_target_agent(self, team_id: Any, target: str):
class RefillPopper (line 77) | class RefillPopper:
method __init__ (line 78) | def __init__(self, original_list, np_random=None):
method pop (line 84) | def pop(self):
class TeamLoader (line 92) | class TeamLoader(spawn.SequentialLoader):
method __init__ (line 93) | def __init__(self, config, np_random,
method get_spawn_position (line 115) | def get_spawn_position(self, agent_id):
function spawn_team_together (line 119) | def spawn_team_together(config, num_teams):
FILE: nmmo/lib/utils.py
class staticproperty (line 10) | class staticproperty(property):
method __get__ (line 11) | def __get__(self, cls, owner):
class classproperty (line 14) | class classproperty(object):
method __init__ (line 15) | def __init__(self, f):
method __get__ (line 17) | def __get__(self, obj, owner):
class Iterable (line 20) | class Iterable(type):
method __iter__ (line 21) | def __iter__(cls):
method values (line 31) | def values(cls):
class StaticIterable (line 34) | class StaticIterable(type):
method __iter__ (line 35) | def __iter__(cls):
class NameComparable (line 45) | class NameComparable(type):
method __hash__ (line 46) | def __hash__(self):
method __eq__ (line 49) | def __eq__(self, other):
method __ne__ (line 52) | def __ne__(self, other):
method __lt__ (line 55) | def __lt__(self, other):
method __le__ (line 58) | def __le__(self, other):
method __gt__ (line 61) | def __gt__(self, other):
method __ge__ (line 64) | def __ge__(self, other):
class IterableNameComparable (line 67) | class IterableNameComparable(Iterable, NameComparable):
function linf (line 70) | def linf(pos1, pos2):
function linf_single (line 75) | def linf_single(pos1, pos2):
function in_bounds (line 80) | def in_bounds(r, c, shape, border=0):
function l1_map (line 89) | def l1_map(size):
function get_hash_embedding (line 96) | def get_hash_embedding(func, embed_dim):
function identify_closest_target (line 114) | def identify_closest_target(entity):
FILE: nmmo/lib/vec_noise.py
function snoise2 (line 39) | def snoise2(x, y):
FILE: nmmo/minigames/center_race.py
class RacetoCenter (line 9) | class RacetoCenter(Game):
method __init__ (line 12) | def __init__(self, env, sampling_weight=None):
method map_size (line 27) | def map_size(self):
method set_map_size (line 30) | def set_map_size(self, map_size):
method is_compatible (line 33) | def is_compatible(self):
method reset (line 36) | def reset(self, np_random, map_dict, tasks=None):
method _set_config (line 44) | def _set_config(self):
method _determine_difficulty (line 64) | def _determine_difficulty(self):
method _set_realm (line 73) | def _set_realm(self, map_dict):
method _define_tasks (line 77) | def _define_tasks(self):
method _process_dead_players (line 81) | def _process_dead_players(self, terminated, dead_players):
method winning_score (line 88) | def winning_score(self):
method _check_winners (line 95) | def _check_winners(self, terminated):
method test (line 99) | def test(env, horizon=30, seed=0):
FILE: nmmo/minigames/comm_together.py
function seek_task (line 9) | def seek_task(within_dist):
class CommTogether (line 15) | class CommTogether(TeamBattle):
method __init__ (line 18) | def __init__(self, env, sampling_weight=None):
method required_systems (line 37) | def required_systems(self):
method map_size (line 41) | def map_size(self):
method set_map_size (line 44) | def set_map_size(self, map_size):
method set_spawn_immunity (line 47) | def set_spawn_immunity(self, spawn_immunity):
method set_grass_map (line 50) | def set_grass_map(self, grass_map):
method is_compatible (line 53) | def is_compatible(self):
method reset (line 56) | def reset(self, np_random, map_dict, tasks=None):
method _set_config (line 62) | def _set_config(self):
method _determine_difficulty (line 80) | def _determine_difficulty(self):
method _set_realm (line 93) | def _set_realm(self, map_dict):
method _define_tasks (line 97) | def _define_tasks(self):
method _process_dead_players (line 101) | def _process_dead_players(self, terminated, dead_players):
method _check_winners (line 107) | def _check_winners(self, terminated):
method winning_score (line 112) | def winning_score(self):
method test (line 120) | def test(env, horizon=30, seed=0):
FILE: nmmo/minigames/king_hill.py
function seize_task (line 8) | def seize_task(dur_to_win):
class KingoftheHill (line 14) | class KingoftheHill(TeamBattle):
method __init__ (line 17) | def __init__(self, env, sampling_weight=None):
method seize_duration (line 34) | def seize_duration(self):
method set_seize_duration (line 37) | def set_seize_duration(self, seize_duration):
method is_compatible (line 40) | def is_compatible(self):
method reset (line 43) | def reset(self, np_random, map_dict, tasks=None):
method _set_config (line 48) | def _set_config(self):
method _determine_difficulty (line 67) | def _determine_difficulty(self):
method _set_realm (line 77) | def _set_realm(self, map_dict):
method _define_tasks (line 83) | def _define_tasks(self):
method winning_score (line 88) | def winning_score(self):
method _check_winners (line 98) | def _check_winners(self, terminated):
method test (line 133) | def test(env, horizon=30, seed=0):
FILE: nmmo/minigames/radio_raid.py
function hunt_task (line 9) | def hunt_task(num_npc):
class RadioRaid (line 15) | class RadioRaid(TeamBattle):
method __init__ (line 19) | def __init__(self, env, sampling_weight=None):
method teams (line 48) | def teams(self):
method goal_num_npc (line 57) | def goal_num_npc(self):
method set_goal_num_npc (line 60) | def set_goal_num_npc(self, goal_num_npc):
method set_grass_map (line 63) | def set_grass_map(self, grass_map):
method is_compatible (line 66) | def is_compatible(self):
method reset (line 69) | def reset(self, np_random, map_dict, tasks=None):
method _set_config (line 75) | def _set_config(self):
method _determine_difficulty (line 99) | def _determine_difficulty(self):
method _set_realm (line 107) | def _set_realm(self, map_dict):
method _define_tasks (line 120) | def _define_tasks(self):
method _process_dead_npcs (line 124) | def _process_dead_npcs(self, dead_npcs):
method _check_winners (line 143) | def _check_winners(self, terminated):
method is_over (line 148) | def is_over(self):
method winning_score (line 153) | def winning_score(self):
method test (line 163) | def test(env, horizon=30, seed=0):
FILE: nmmo/minigames/sandwich.py
function secure_order (line 9) | def secure_order(pos, radius=5):
class Sandwich (line 12) | class Sandwich(TeamBattle):
method __init__ (line 16) | def __init__(self, env, sampling_weight=None):
method teams (line 30) | def teams(self):
method inner_npc_num (line 39) | def inner_npc_num(self):
method set_inner_npc_num (line 42) | def set_inner_npc_num(self, inner_npc_num):
method outer_npc_num (line 46) | def outer_npc_num(self):
method set_outer_npc_num (line 49) | def set_outer_npc_num(self, outer_npc_num):
method set_grass_map (line 52) | def set_grass_map(self, grass_map):
method is_compatible (line 55) | def is_compatible(self):
method reset (line 58) | def reset(self, np_random, map_dict, tasks=None):
method _set_config (line 64) | def _set_config(self):
method _determine_difficulty (line 86) | def _determine_difficulty(self):
method _generate_spawn_locs (line 97) | def _generate_spawn_locs(self):
method _set_realm (line 103) | def _set_realm(self, map_dict):
method _process_dead_npcs (line 132) | def _process_dead_npcs(self, dead_npcs):
method winning_score (line 146) | def winning_score(self):
method _check_winners (line 154) | def _check_winners(self, terminated):
method test (line 161) | def test(env, horizon=30, seed=0):
FILE: nmmo/render/overlay.py
class OverlayRegistry (line 9) | class OverlayRegistry:
method __init__ (line 10) | def __init__(self, realm, renderer):
method init (line 27) | def init(self, *args):
method step (line 33) | def step(self, cmd):
class Overlay (line 49) | class Overlay:
method __init__ (line 55) | def __init__(self, config, realm, renderer, *args):
method update (line 68) | def update(self):
method register (line 75) | def register(self):
class Skills (line 79) | class Skills(Overlay):
method __init__ (line 80) | def __init__(self, config, realm, renderer, *args):
method update (line 87) | def update(self):
method register (line 102) | def register(self):
class Counts (line 128) | class Counts(Overlay):
method __init__ (line 129) | def __init__(self, config, realm, renderer, *args):
method update (line 133) | def update(self):
method register (line 140) | def register(self):
FILE: nmmo/render/render_client.py
class DummyRenderer (line 10) | class DummyRenderer:
method __init__ (line 11) | def __init__(self, realm=None) -> None:
method set_realm (line 22) | def set_realm(self, realm) -> None:
method render_packet (line 26) | def render_packet(self, packet) -> None:
method render_realm (line 34) | def render_realm(self) -> None:
method register (line 78) | def register(self, overlay: np.ndarray) -> None:
FILE: nmmo/render/render_utils.py
function np_encoder (line 8) | def np_encoder(obj):
function normalize (line 12) | def normalize(ary: np.ndarray, norm_std=2):
function clip (line 29) | def clip(ary: np.ndarray):
function make_two_tone (line 42) | def make_two_tone(ary, norm_std=2, preprocess='norm', invert=False, peri...
function patch_packet (line 68) | def patch_packet(packet, realm):
FILE: nmmo/systems/combat.py
function level (line 8) | def level(skills):
function damage_multiplier (line 11) | def damage_multiplier(config, skill, targ):
function attack (line 27) | def attack(realm, attacker, target, skill_fn):
function danger (line 115) | def danger(config, pos):
function spawn (line 128) | def spawn(config, dnger, np_random):
FILE: nmmo/systems/droptable.py
class Fixed (line 1) | class Fixed():
method __init__ (line 2) | def __init__(self, item):
method roll (line 5) | def roll(self, realm, level):
class Drop (line 8) | class Drop:
method __init__ (line 9) | def __init__(self, item, prob):
method roll (line 13) | def roll(self, realm, level):
class Standard (line 22) | class Standard:
method __init__ (line 23) | def __init__(self):
method add (line 26) | def add(self, item, prob=1.0):
method roll (line 29) | def roll(self, realm, level):
class Empty (line 37) | class Empty(Standard):
method roll (line 38) | def roll(self, realm, level):
class Ammunition (line 41) | class Ammunition(Standard):
method __init__ (line 42) | def __init__(self, item):
method roll (line 46) | def roll(self, realm, level):
class Consumable (line 49) | class Consumable(Standard):
method __init__ (line 50) | def __init__(self, item):
method roll (line 54) | def roll(self, realm, level):
FILE: nmmo/systems/exchange.py
class ItemListing (line 27) | class ItemListing:
method __init__ (line 28) | def __init__(self, item: Item, seller, price: int, tick: int):
class Exchange (line 34) | class Exchange:
method __init__ (line 35) | def __init__(self, realm):
method reset (line 41) | def reset(self):
method _list_item (line 45) | def _list_item(self, item: Item, seller, price: int, tick: int):
method unlist_item (line 50) | def unlist_item(self, item: Item):
method _unlist_item (line 54) | def _unlist_item(self, item_id: int):
method step (line 58) | def step(self):
method sell (line 99) | def sell(self, seller, item: Item, price: int, tick: int):
method buy (line 110) | def buy(self, buyer, item: Item):
method packet (line 141) | def packet(self):
FILE: nmmo/systems/inventory.py
class EquipmentSlot (line 6) | class EquipmentSlot:
method __init__ (line 7) | def __init__(self) -> None:
method equip (line 10) | def equip(self, item: Item.Item) -> None:
method unequip (line 13) | def unequip(self) -> None:
class Equipment (line 18) | class Equipment:
method __init__ (line 19) | def __init__(self):
method total (line 26) | def total(self, lambda_getter):
method __iter__ (line 32) | def __iter__(self):
method conditional_packet (line 37) | def conditional_packet(self, packet, slot_name: str, slot: EquipmentSl...
method item_level (line 42) | def item_level(self):
method melee_attack (line 46) | def melee_attack(self):
method range_attack (line 50) | def range_attack(self):
method mage_attack (line 54) | def mage_attack(self):
method melee_defense (line 58) | def melee_defense(self):
method range_defense (line 62) | def range_defense(self):
method mage_defense (line 66) | def mage_defense(self):
method packet (line 70) | def packet(self):
class Inventory (line 92) | class Inventory:
method __init__ (line 93) | def __init__(self, realm, entity):
method space (line 109) | def space(self):
method has_stack (line 112) | def has_stack(self, signature: Tuple) -> bool:
method packet (line 115) | def packet(self):
method __iter__ (line 124) | def __iter__(self):
method receive (line 128) | def receive(self, item: Item.Item) -> bool:
method remove (line 163) | def remove(self, item, quantity=None):
method _remove (line 188) | def _remove(self, item):
FILE: nmmo/systems/item.py
class Item (line 69) | class Item(ItemState):
method register (line 74) | def register(item_type):
method item_class (line 80) | def item_class(type_id: int):
method __init__ (line 83) | def __init__(self, realm, level,
method destroy (line 111) | def destroy(self):
method packet (line 122) | def packet(self):
method _level (line 137) | def _level(self, entity):
method level_gt (line 142) | def level_gt(self, entity):
method use (line 145) | def use(self, entity) -> bool:
class Stack (line 148) | class Stack:
method signature (line 150) | def signature(self):
class Equipment (line 153) | class Equipment(Item):
method packet (line 155) | def packet(self):
method color (line 160) | def color(self):
method unequip (line 175) | def unequip(self, equip_slot):
method equip (line 180) | def equip(self, entity, equip_slot):
method _slot (line 188) | def _slot(self, entity):
method use (line 191) | def use(self, entity):
class Armor (line 204) | class Armor(Equipment, ABC):
method __init__ (line 205) | def __init__(self, realm, level, **kwargs):
class Hat (line 213) | class Hat(Armor):
method _slot (line 215) | def _slot(self, entity):
class Top (line 217) | class Top(Armor):
method _slot (line 219) | def _slot(self, entity):
class Bottom (line 221) | class Bottom(Armor):
method _slot (line 223) | def _slot(self, entity):
class Weapon (line 227) | class Weapon(Equipment):
method __init__ (line 228) | def __init__(self, realm, level, **kwargs):
method _slot (line 234) | def _slot(self, entity):
class Spear (line 237) | class Spear(Weapon):
method __init__ (line 240) | def __init__(self, realm, level, **kwargs):
method _level (line 244) | def _level(self, entity):
class Bow (line 246) | class Bow(Weapon):
method __init__ (line 249) | def __init__(self, realm, level, **kwargs):
method _level (line 253) | def _level(self, entity):
class Wand (line 255) | class Wand(Weapon):
method __init__ (line 258) | def __init__(self, realm, level, **kwargs):
method _level (line 262) | def _level(self, entity):
class Tool (line 266) | class Tool(Equipment):
method __init__ (line 267) | def __init__(self, realm, level, **kwargs):
method _slot (line 276) | def _slot(self, entity):
class Rod (line 278) | class Rod(Tool):
method _level (line 280) | def _level(self, entity):
class Gloves (line 282) | class Gloves(Tool):
method _level (line 284) | def _level(self, entity):
class Pickaxe (line 286) | class Pickaxe(Tool):
method _level (line 288) | def _level(self, entity):
class Axe (line 290) | class Axe(Tool):
method _level (line 292) | def _level(self, entity):
class Chisel (line 294) | class Chisel(Tool):
method _level (line 296) | def _level(self, entity):
class Ammunition (line 300) | class Ammunition(Equipment, Stack):
method __init__ (line 301) | def __init__(self, realm, level, **kwargs):
method _slot (line 307) | def _slot(self, entity):
method fire (line 310) | def fire(self, entity) -> int:
class Whetstone (line 324) | class Whetstone(Ammunition):
method __init__ (line 327) | def __init__(self, realm, level, **kwargs):
method _level (line 331) | def _level(self, entity):
method damage (line 335) | def damage(self):
class Arrow (line 338) | class Arrow(Ammunition):
method __init__ (line 341) | def __init__(self, realm, level, **kwargs):
method _level (line 345) | def _level(self, entity):
method damage (line 349) | def damage(self):
class Runes (line 352) | class Runes(Ammunition):
method __init__ (line 355) | def __init__(self, realm, level, **kwargs):
method _level (line 359) | def _level(self, entity):
method damage (line 363) | def damage(self):
class Consumable (line 369) | class Consumable(Item):
method use (line 370) | def use(self, entity) -> bool:
class Ration (line 381) | class Ration(Consumable):
method __init__ (line 384) | def __init__(self, realm, level, **kwargs):
method _apply_effects (line 390) | def _apply_effects(self, entity):
class Potion (line 394) | class Potion(Consumable):
method __init__ (line 397) | def __init__(self, realm, level, **kwargs):
method _apply_effects (line 403) | def _apply_effects(self, entity):
FILE: nmmo/systems/skill.py
class ExperienceCalculator (line 13) | class ExperienceCalculator:
method __init__ (line 14) | def __init__(self, config):
method exp_at_level (line 23) | def exp_at_level(self, level):
method level_at_exp (line 28) | def level_at_exp(self, exp):
class SkillGroup (line 33) | class SkillGroup:
method __init__ (line 34) | def __init__(self, realm, entity):
method update (line 42) | def update(self):
method packet (line 46) | def packet(self):
class Skill (line 53) | class Skill(abc.ABC):
method __init__ (line 54) | def __init__(self, skill_group: SkillGroup):
method packet (line 63) | def packet(self):
method add_xp (line 69) | def add_xp(self, xp):
method set_experience_by_level (line 78) | def set_experience_by_level(self, level):
method level (line 83) | def level(self):
method exp (line 88) | def exp(self):
class CombatSkill (line 93) | class CombatSkill(Skill):
method update (line 94) | def update(self):
class NonCombatSkill (line 97) | class NonCombatSkill(Skill):
method __init__ (line 98) | def __init__(self, skill_group: SkillGroup):
method level (line 103) | def level(self):
method exp (line 107) | def exp(self):
class HarvestSkill (line 110) | class HarvestSkill(NonCombatSkill):
method process_drops (line 111) | def process_drops(self, matl, drop_table):
method harvest (line 133) | def harvest(self, matl, deplete=True):
method harvest_adjacent (line 147) | def harvest_adjacent(self, matl, deplete=True):
class AmmunitionSkill (line 168) | class AmmunitionSkill(HarvestSkill):
method process_drops (line 169) | def process_drops(self, matl, drop_table):
class ConsumableSkill (line 175) | class ConsumableSkill(HarvestSkill):
method process_drops (line 176) | def process_drops(self, matl, drop_table):
class Basic (line 183) | class Basic(SkillGroup):
method __init__ (line 184) | def __init__(self, realm, entity):
method basic_level (line 191) | def basic_level(self):
class Harvest (line 195) | class Harvest(SkillGroup):
method __init__ (line 196) | def __init__(self, realm, entity):
method harvest_level (line 206) | def harvest_level(self):
class Combat (line 213) | class Combat(SkillGroup):
method __init__ (line 214) | def __init__(self, realm, entity):
method packet (line 221) | def packet(self):
method combat_level (line 228) | def combat_level(self):
method apply_damage (line 233) | def apply_damage(self, style):
method receive_damage (line 238) | def receive_damage(self, dmg):
class Skills (line 241) | class Skills(Basic, Harvest, Combat):
class Melee (line 245) | class Melee(CombatSkill):
method level (line 249) | def level(self):
method exp (line 253) | def exp(self):
class Range (line 256) | class Range(CombatSkill):
method level (line 260) | def level(self):
method exp (line 264) | def exp(self):
class Mage (line 267) | class Mage(CombatSkill):
method level (line 271) | def level(self):
method exp (line 275) | def exp(self):
class DummyValue (line 285) | class DummyValue:
method __init__ (line 286) | def __init__(self, val=0):
method update (line 289) | def update(self, val):
class Water (line 292) | class Water(HarvestSkill):
method update (line 293) | def update(self):
class Food (line 314) | class Food(HarvestSkill):
method update (line 315) | def update(self):
class Fishing (line 336) | class Fishing(ConsumableSkill):
method level (line 340) | def level(self):
method exp (line 344) | def exp(self):
method update (line 347) | def update(self):
class Herbalism (line 350) | class Herbalism(ConsumableSkill):
method level (line 354) | def level(self):
method exp (line 358) | def exp(self):
method update (line 361) | def update(self):
class Prospecting (line 364) | class Prospecting(AmmunitionSkill):
method level (line 368) | def level(self):
method exp (line 372) | def exp(self):
method update (line 375) | def update(self):
class Carving (line 378) | class Carving(AmmunitionSkill):
method level (line 382) | def level(self):
method exp (line 386) | def exp(self):
method update (line 389) | def update(self,):
class Alchemy (line 392) | class Alchemy(AmmunitionSkill):
method level (line 396) | def level(self):
method exp (line 400) | def exp(self):
method update (line 403) | def update(self):
FILE: nmmo/task/base_predicates.py
function norm (line 15) | def norm(progress):
function Success (line 18) | def Success(gs: GameState, subject: Group):
function TickGE (line 23) | def TickGE(gs: GameState, subject: Group, num_tick: int = None):
function CanSeeTile (line 31) | def CanSeeTile(gs: GameState, subject: Group, tile_type: type[Material]):
function StayAlive (line 36) | def StayAlive(gs: GameState, subject: Group):
function AllDead (line 41) | def AllDead(gs: GameState, subject: Group):
function CheckAgentStatus (line 46) | def CheckAgentStatus(gs: GameState, subject: Group, target: Iterable[int...
function OccupyTile (line 59) | def OccupyTile(gs: GameState, subject: Group, row: int, col: int):
function CanSeeAgent (line 64) | def CanSeeAgent(gs: GameState, subject: Group, target: int):
function CanSeeGroup (line 69) | def CanSeeGroup(gs: GameState, subject: Group, target: Iterable[int]):
function DistanceTraveled (line 76) | def DistanceTraveled(gs: GameState, subject: Group, dist: int):
function AttainSkill (line 87) | def AttainSkill(gs: GameState, subject: Group,
function GainExperience (line 97) | def GainExperience(gs: GameState, subject: Group,
function CountEvent (line 103) | def CountEvent(gs: GameState, subject: Group, event: str, N: int):
function ScoreHit (line 109) | def ScoreHit(gs: GameState, subject: Group, combat_style: type[Skill], N...
function DefeatEntity (line 116) | def DefeatEntity(gs: GameState, subject: Group, agent_type: str, level: ...
function HoardGold (line 129) | def HoardGold(gs: GameState, subject: Group, amount: int):
function EarnGold (line 134) | def EarnGold(gs: GameState, subject: Group, amount: int):
function SpendGold (line 140) | def SpendGold(gs: GameState, subject: Group, amount: int):
function MakeProfit (line 145) | def MakeProfit(gs: GameState, subject: Group, amount: int):
function InventorySpaceGE (line 152) | def InventorySpaceGE(gs: GameState, subject: Group, space: int):
function OwnItem (line 159) | def OwnItem(gs: GameState, subject: Group, item: type[Item], level: int,...
function EquipItem (line 167) | def EquipItem(gs: GameState, subject: Group, item: type[Item], level: in...
function FullyArmed (line 178) | def FullyArmed(gs: GameState, subject: Group,
function ConsumeItem (line 202) | def ConsumeItem(gs: GameState, subject: Group, item: type[Item], level: ...
function HarvestItem (line 209) | def HarvestItem(gs: GameState, subject: Group, item: type[Item], level: ...
function FireAmmo (line 216) | def FireAmmo(gs: GameState, subject: Group, item: type[Item], level: int...
function ListItem (line 223) | def ListItem(gs: GameState, subject: Group, item: type[Item], level: int...
function BuyItem (line 230) | def BuyItem(gs: GameState, subject: Group, item: type[Item], level: int,...
function ProgressTowardCenter (line 241) | def ProgressTowardCenter(gs, subject):
function AllMembersWithinRange (line 258) | def AllMembersWithinRange(gs: GameState, subject: Group, dist: int):
function SeizeTile (line 279) | def SeizeTile(gs: GameState, subject: Group, row: int, col: int, num_tic...
function SeizeCenter (line 306) | def SeizeCenter(gs: GameState, subject: Group, num_ticks: int,
function SeizeQuadCenter (line 311) | def SeizeQuadCenter(gs: GameState, subject: Group, num_ticks: int, quadr...
function ProtectLeader (line 327) | def ProtectLeader(gs, subject, target_protect: int, target_destroy: Iter...
FILE: nmmo/task/game_state.py
class GameState (line 32) | class GameState:
method entity_or_none (line 55) | def entity_or_none(self, ent_id):
method where_in_id (line 60) | def where_in_id(self, data_type, subject: Iterable[int]):
method get_subject_view (line 79) | def get_subject_view(self, subject: Group):
method clear_cache (line 84) | def clear_cache(self):
class CachedProperty (line 93) | class CachedProperty:
method __init__ (line 94) | def __init__(self, func):
method __get__ (line 100) | def __get__(self, instance, owner):
class ArrayView (line 107) | class ArrayView(ABC):
method __init__ (line 108) | def __init__(self,
method __len__ (line 122) | def __len__(self):
method get_attribute (line 126) | def get_attribute(self, attr) -> np.ndarray:
method __getattr__ (line 129) | def __getattr__(self, attr) -> np.ndarray:
class ItemView (line 137) | class ItemView(ArrayView):
method __init__ (line 138) | def __init__(self, gs: GameState, subject: Group, arr: np.ndarray):
method get_attribute (line 142) | def get_attribute(self, attr) -> np.ndarray:
class EntityView (line 145) | class EntityView(ArrayView):
method __init__ (line 146) | def __init__(self, gs: GameState, subject: Group, arr: np.ndarray):
method get_attribute (line 149) | def get_attribute(self, attr) -> np.ndarray:
class EventView (line 152) | class EventView(ArrayView):
method __init__ (line 153) | def __init__(self, gs: GameState, subject: Group, arr: np.ndarray):
method get_attribute (line 156) | def get_attribute(self, attr) -> np.ndarray:
class TileView (line 162) | class TileView(ArrayView):
method __init__ (line 163) | def __init__(self, gs: GameState, subject: Group, arr: np.ndarray):
method get_attribute (line 166) | def get_attribute(self, attr) -> np.ndarray:
class EventCodeView (line 169) | class EventCodeView(ArrayView):
method __init__ (line 170) | def __init__(self,
method get_attribute (line 177) | def get_attribute(self, attr) -> np.ndarray:
class GroupObsView (line 181) | class GroupObsView:
method __init__ (line 182) | def __init__(self, gs: GameState, subject: Group):
method tile (line 190) | def tile(self):
method __getattr__ (line 193) | def __getattr__(self, attr):
class GroupView (line 196) | class GroupView:
method __init__ (line 197) | def __init__(self, gs: GameState, subject: Group):
method obs (line 203) | def obs(self):
method _sbj_ent (line 207) | def _sbj_ent(self):
method entity (line 211) | def entity(self):
method _sbj_item (line 215) | def _sbj_item(self):
method item (line 219) | def item(self):
method _sbj_event (line 223) | def _sbj_event(self):
method event (line 227) | def event(self):
method __getattribute__ (line 230) | def __getattribute__(self, attr):
class GameStateGenerator (line 253) | class GameStateGenerator:
method __init__ (line 254) | def __init__(self, realm: Realm, config: Config):
method generate (line 261) | def generate(self, realm: Realm, env_obs: Dict[int, Observation]) -> G...
function precompute_index (line 284) | def precompute_index(table, id_col):
FILE: nmmo/task/group.py
class Group (line 10) | class Group(Sequence, Set):
method __init__ (line 13) | def __init__(self,
method agents (line 32) | def agents(self):
method union (line 35) | def union(self, o: Group):
method intersection (line 38) | def intersection(self, o: Group):
method __eq__ (line 41) | def __eq__(self, o):
method __len__ (line 44) | def __len__(self):
method __hash__ (line 47) | def __hash__(self):
method __getitem__ (line 50) | def __getitem__(self, key):
method __contains__ (line 55) | def __contains__(self, key):
method __str__ (line 60) | def __str__(self) -> str:
method __int__ (line 63) | def __int__(self) -> int:
method __copy__ (line 67) | def __copy__(self):
method __deepcopy__ (line 69) | def __deepcopy__(self, memo):
method description (line 72) | def description(self) -> Dict:
method clear_prev_state (line 79) | def clear_prev_state(self) -> None:
method update (line 87) | def update(self, gs: GameState) -> None:
method __getattr__ (line 92) | def __getattr__(self, attr):
function union (line 95) | def union(*groups: Group) -> Group:
function complement (line 104) | def complement(group: Group, universe: Group) -> Group:
FILE: nmmo/task/predicate_api.py
class InvalidPredicateDefinition (line 15) | class InvalidPredicateDefinition(Exception):
class Predicate (line 18) | class Predicate(ABC):
method __init__ (line 21) | def __init__(self,
method __call__ (line 37) | def __call__(self, gs: GameState) -> float:
method close (line 58) | def close(self):
method _evaluate (line 64) | def _evaluate(self, gs: GameState) -> float:
method _make_name (line 70) | def _make_name(self, class_name, args, kwargs) -> str:
method __str__ (line 77) | def __str__(self):
method get_source_code (line 81) | def get_source_code(self) -> str:
method get_signature (line 87) | def get_signature(self) -> List:
method args (line 93) | def args(self):
method kwargs (line 97) | def kwargs(self):
method subject (line 101) | def subject(self):
method create_task (line 104) | def create_task(self,
method __and__ (line 119) | def __and__(self, other):
method __or__ (line 121) | def __or__(self, other):
method __invert__ (line 123) | def __invert__(self):
method __add__ (line 125) | def __add__(self, other):
method __radd__ (line 127) | def __radd__(self, other):
method __sub__ (line 129) | def __sub__(self, other):
method __rsub__ (line 131) | def __rsub__(self, other):
method __mul__ (line 133) | def __mul__(self, other):
method __rmul__ (line 135) | def __rmul__(self, other):
function arg_to_string (line 139) | def arg_to_string(arg):
function make_predicate (line 148) | def make_predicate(fn: Callable) -> Type[Predicate]:
class PredicateOperator (line 176) | class PredicateOperator(Predicate):
method __init__ (line 177) | def __init__(self, n, *predicates: Union[Predicate, Real], subject: Gr...
method check (line 192) | def check(self, config: Config) -> bool:
method sample (line 196) | def sample(self, config: Config, cls: Type[PredicateOperator], **kwargs):
method get_source_code (line 202) | def get_source_code(self) -> str:
method get_signature (line 215) | def get_signature(self):
method args (line 220) | def args(self):
method kwargs (line 225) | def kwargs(self):
class OR (line 234) | class OR(PredicateOperator, Predicate):
method __init__ (line 235) | def __init__(self, *predicates: Predicate, subject: Group=None):
method _evaluate (line 237) | def _evaluate(self, gs: GameState) -> float:
method sample (line 240) | def sample(self, config: Config, **kwargs):
class AND (line 243) | class AND(PredicateOperator, Predicate):
method __init__ (line 244) | def __init__(self, *predicates: Predicate, subject: Group=None):
method _evaluate (line 246) | def _evaluate(self, gs: GameState) -> float:
method sample (line 249) | def sample(self, config: Config, **kwargs):
class NOT (line 252) | class NOT(PredicateOperator, Predicate):
method __init__ (line 253) | def __init__(self, predicate: Predicate, subject: Group=None):
method _evaluate (line 255) | def _evaluate(self, gs: GameState) -> float:
method sample (line 257) | def sample(self, config: Config, **kwargs):
class ADD (line 260) | class ADD(PredicateOperator, Predicate):
method __init__ (line 261) | def __init__(self, *predicate: Union[Predicate, Real], subject: Group=...
method _evaluate (line 263) | def _evaluate(self, gs: GameState) -> float:
method sample (line 265) | def sample(self, config: Config, **kwargs):
class SUB (line 268) | class SUB(PredicateOperator, Predicate):
method __init__ (line 269) | def __init__(self, p: Predicate, q: Union[Predicate, Real], subject: G...
method _evaluate (line 271) | def _evaluate(self, gs: GameState) -> float:
method sample (line 273) | def sample(self, config: Config, **kwargs):
class MUL (line 276) | class MUL(PredicateOperator, Predicate):
method __init__ (line 277) | def __init__(self, *predicate: Union[Predicate, Real], subject: Group=...
method _evaluate (line 279) | def _evaluate(self, gs: GameState) -> float:
method sample (line 284) | def sample(self, config: Config, **kwargs):
FILE: nmmo/task/task_api.py
class Task (line 13) | class Task(ABC):
method __init__ (line 17) | def __init__(self,
method reset (line 39) | def reset(self):
method close (line 48) | def close(self):
method assignee (line 55) | def assignee(self) -> Tuple[int]:
method completed (line 59) | def completed(self) -> bool:
method progress (line 63) | def progress(self) -> float:
method reward_multiplier (line 67) | def reward_multiplier(self) -> float:
method reward_signal_count (line 71) | def reward_signal_count(self) -> int:
method embedding (line 75) | def embedding(self):
method set_embedding (line 78) | def set_embedding(self, embedding):
method _map_progress_to_reward (line 81) | def _map_progress_to_reward(self, gs: GameState) -> float:
method compute_rewards (line 98) | def compute_rewards(self, gs: GameState) -> Tuple[Dict[int, float], Di...
method _make_name (line 119) | def _make_name(self, class_name, **kwargs) -> str:
method __str__ (line 125) | def __str__(self):
method subject (line 129) | def subject(self):
method get_source_code (line 134) | def get_source_code(self):
method get_signature (line 139) | def get_signature(self):
method args (line 146) | def args(self):
method kwargs (line 153) | def kwargs(self):
method progress_info (line 160) | def progress_info(self):
class OngoingTask (line 172) | class OngoingTask(Task):
method _map_progress_to_reward (line 173) | def _map_progress_to_reward(self, gs: GameState) -> float:
class HoldDurationTask (line 182) | class HoldDurationTask(Task):
method __init__ (line 183) | def __init__(self,
method _reset_timer (line 192) | def _reset_timer(self):
method reset (line 196) | def reset(self):
method _map_progress_to_reward (line 200) | def _map_progress_to_reward(self, gs: GameState) -> float:
function make_same_task (line 225) | def make_same_task(pred_cls: Union[Type[Predicate], Callable],
function nmmo_default_task (line 244) | def nmmo_default_task(agent_list: Iterable[int], test_mode=None) -> List...
FILE: nmmo/task/task_spec.py
class TaskSpec (line 39) | class TaskSpec:
method __post_init__ (line 50) | def __post_init__(self):
method name (line 63) | def name(self):
function make_task_from_spec (line 76) | def make_task_from_spec(assign_to: Union[Iterable[int], Dict],
function check_task_spec (line 153) | def check_task_spec(spec_list: List[TaskSpec], debug=False) -> List[Dict]:
FILE: scripted/attack.py
function closestTarget (line 10) | def closestTarget(config, ob: Observation):
function attacker (line 32) | def attacker(config, ob: Observation):
function target (line 46) | def target(config, actions, style, targetID):
FILE: scripted/baselines.py
class Scripted (line 16) | class Scripted(nmmo.Scripted):
method __init__ (line 22) | def __init__(self, config, idx):
method policy (line 38) | def policy(self):
method forage_criterion (line 42) | def forage_criterion(self) -> bool:
method forage (line 47) | def forage(self):
method gather (line 53) | def gather(self, resource):
method explore (line 57) | def explore(self):
method downtime (line 62) | def downtime(self):
method evade (line 66) | def evade(self):
method attack (line 73) | def attack(self):
method target_weak (line 80) | def target_weak(self): # pylint: disable=inconsistent-return-statements
method scan_agents (line 95) | def scan_agents(self):
method adaptive_control_and_targeting (line 112) | def adaptive_control_and_targeting(self, explore=True):
method process_inventory (line 129) | def process_inventory(self):
method upgrade_heuristic (line 180) | def upgrade_heuristic(self, current_level, upgrade_level, price):
method process_market (line 183) | def process_market(self):
method equip (line 219) | def equip(self, items: set):
method consume (line 233) | def consume(self):
method sell (line 250) | def sell(self, keep_k: dict, keep_best: set):
method buy (line 276) | def buy(self, buy_k: dict, buy_upgrade: set):
method exchange (line 301) | def exchange(self):
method use (line 309) | def use(self):
method __call__ (line 314) | def __call__(self, observation: Observation):
class Sleeper (line 354) | class Sleeper(Scripted):
method __call__ (line 356) | def __call__(self, obs):
class Random (line 359) | class Random(Scripted):
method __call__ (line 361) | def __call__(self, obs):
class Meander (line 367) | class Meander(Scripted):
method __call__ (line 369) | def __call__(self, obs):
class Explore (line 375) | class Explore(Scripted):
method __call__ (line 377) | def __call__(self, obs):
class Forage (line 384) | class Forage(Scripted):
method __call__ (line 386) | def __call__(self, obs):
class Combat (line 396) | class Combat(Scripted):
method __init__ (line 398) | def __init__(self, config, idx):
method supplies (line 403) | def supplies(self):
method wishlist (line 411) | def wishlist(self):
method __call__ (line 420) | def __call__(self, obs):
class Gather (line 430) | class Gather(Scripted):
method __init__ (line 432) | def __init__(self, config, idx):
method supplies (line 437) | def supplies(self):
method wishlist (line 444) | def wishlist(self):
method __call__ (line 452) | def __call__(self, obs):
class Fisher (line 464) | class Fisher(Gather):
method __init__ (line 465) | def __init__(self, config, idx):
class Herbalist (line 471) | class Herbalist(Gather):
method __init__ (line 472) | def __init__(self, config, idx):
class Prospector (line 478) | class Prospector(Gather):
method __init__ (line 479) | def __init__(self, config, idx):
class Carver (line 485) | class Carver(Gather):
method __init__ (line 486) | def __init__(self, config, idx):
class Alchemist (line 492) | class Alchemist(Gather):
method __init__ (line 493) | def __init__(self, config, idx):
class Melee (line 499) | class Melee(Combat):
method __init__ (line 500) | def __init__(self, config, idx):
class Range (line 507) | class Range(Combat):
method __init__ (line 508) | def __init__(self, config, idx):
class Mage (line 515) | class Mage(Combat):
method __init__ (line 516) | def __init__(self, config, idx):
FILE: scripted/move.py
function inSight (line 10) | def inSight(dr, dc, vision):
function rand (line 14) | def rand(config, ob, actions, np_random):
function towards (line 18) | def towards(direction, np_random):
function pathfind (line 30) | def pathfind(config, ob, actions, rr, cc, np_random):
function meander (line 35) | def meander(config, ob, actions, np_random):
function explore (line 51) | def explore(config, ob, actions, r, c, np_random):
function evade (line 61) | def evade(config, ob: Observation, actions, attacker, np_random):
function forageDijkstra (line 66) | def forageDijkstra(config, ob: Observation, actions,
function findResource (line 134) | def findResource(config, ob: Observation, resource):
function gatherAStar (line 145) | def gatherAStar(config, ob, actions, resource, np_random, cutoff=100):
function gatherBFS (line 159) | def gatherBFS(config, ob: Observation, actions, resource, np_random, cut...
function aStar (line 226) | def aStar(config, ob: Observation, actions, rr, cc, cutoff=100):
FILE: tests/action/test_ammo_use.py
class TestAmmoUse (line 15) | class TestAmmoUse(ScriptedTestTemplate):
method setUpClass (line 19) | def setUpClass(cls):
method _assert_action_targets_zero (line 26) | def _assert_action_targets_zero(self, gym_obs):
method test_spawn_immunity (line 36) | def test_spawn_immunity(self):
method test_ammo_fire_all (line 56) | def test_ammo_fire_all(self):
method test_use_ammo_only_when_attack_style_match (line 144) | def test_use_ammo_only_when_attack_style_match(self):
method test_cannot_use_listed_items (line 168) | def test_cannot_use_listed_items(self):
method test_receive_extra_ammo_swap (line 234) | def test_receive_extra_ammo_swap(self):
method test_use_ration_potion (line 332) | def test_use_ration_potion(self):
FILE: tests/action/test_destroy_give_gold.py
class TestDestroyGiveGold (line 15) | class TestDestroyGiveGold(ScriptedTestTemplate):
method setUpClass (line 19) | def setUpClass(cls):
method test_destroy (line 34) | def test_destroy(self):
method test_give_tile_npc (line 90) | def test_give_tile_npc(self):
method test_give_equipped_listed (line 131) | def test_give_equipped_listed(self):
method test_give_full_inventory (line 192) | def test_give_full_inventory(self):
method test_give_gold (line 238) | def test_give_gold(self):
FILE: tests/action/test_monkey_action.py
function make_random_actions (line 16) | def make_random_actions(config, ent_obs):
function filter_item_actions (line 32) | def filter_item_actions(actions, use_str_key=False):
class TestMonkeyAction (line 67) | class TestMonkeyAction(unittest.TestCase):
method setUpClass (line 69) | def setUpClass(cls):
method rollout_with_seed (line 75) | def rollout_with_seed(config, seed, use_str_key=False):
method test_monkey_action (line 87) | def test_monkey_action(self):
method test_monkey_action_with_str_key (line 93) | def test_monkey_action_with_str_key(self):
FILE: tests/action/test_sell_buy.py
class TestSellBuy (line 15) | class TestSellBuy(ScriptedTestTemplate):
method setUpClass (line 19) | def setUpClass(cls):
method test_sell_buy (line 33) | def test_sell_buy(self):
FILE: tests/conftest.py
function pytest_benchmark_scale_unit (line 7) | def pytest_benchmark_scale_unit(config, unit, benchmarks, best, worst, s...
FILE: tests/core/test_config.py
class Config (line 7) | class Config(cfg.Config, cfg.Terrain, cfg.Combat):
class TestConfig (line 10) | class TestConfig(unittest.TestCase):
method test_config_attr_set_episode (line 11) | def test_config_attr_set_episode(self):
method test_cannot_change_immutable_attr (line 21) | def test_cannot_change_immutable_attr(self):
method test_cannot_change_obs_attr (line 26) | def test_cannot_change_obs_attr(self):
method test_cannot_use_noninit_system (line 31) | def test_cannot_use_noninit_system(self):
FILE: tests/core/test_cython_masks.py
class TestCythonMasks (line 14) | class TestCythonMasks(unittest.TestCase):
method setUpClass (line 16) | def setUpClass(cls):
method test_move_mask (line 28) | def test_move_mask(self):
method test_attack_mask (line 45) | def test_attack_mask(self):
FILE: tests/core/test_entity.py
class MockRealm (line 10) | class MockRealm:
method __init__ (line 11) | def __init__(self):
class TestEntity (line 19) | class TestEntity(unittest.TestCase):
method test_entity (line 20) | def test_entity(self):
method test_query_by_ids (line 47) | def test_query_by_ids(self):
method test_recon_resurrect (line 62) | def test_recon_resurrect(self):
FILE: tests/core/test_env.py
class Config (line 22) | class Config(nmmo.config.Small, nmmo.config.AllGameSystems):
class TestEnv (line 28) | class TestEnv(unittest.TestCase):
method setUpClass (line 30) | def setUpClass(cls):
method test_action_space (line 34) | def test_action_space(self):
method test_observations (line 41) | def test_observations(self):
method _validate_tiles (line 92) | def _validate_tiles(self, obs, realm: Realm):
method _validate_entitites (line 101) | def _validate_entitites(self, player_id, obs, realm: Realm, entity_loc...
method _validate_inventory (line 133) | def _validate_inventory(self, player_id, obs, realm: Realm):
method _validate_market (line 139) | def _validate_market(self, obs, realm: Realm):
method _validate_items (line 145) | def _validate_items(self, items_dict, item_obs):
method test_clean_item_after_reset (line 157) | def test_clean_item_after_reset(self):
method test_truncated (line 175) | def test_truncated(self):
FILE: tests/core/test_game_api.py
class TeamConfig (line 12) | class TeamConfig(nmmo.config.Small, nmmo.config.AllGameSystems):
class TestGameApi (line 18) | class TestGameApi(unittest.TestCase):
method setUpClass (line 20) | def setUpClass(cls):
method test_num_agents_in_teams (line 24) | def test_num_agents_in_teams(self):
method test_agent_training_game (line 31) | def test_agent_training_game(self):
method test_team_training_game_spawn (line 59) | def test_team_training_game_spawn(self):
method test_team_battle_mode (line 84) | def test_team_battle_mode(self):
method test_competition_winner_task_completed (line 104) | def test_competition_winner_task_completed(self):
method test_game_via_config (line 118) | def test_game_via_config(self):
method test_game_set_next_task (line 131) | def test_game_set_next_task(self):
FILE: tests/core/test_gym_obs_spaces.py
class TestGymObsSpaces (line 11) | class TestGymObsSpaces(unittest.TestCase):
method _is_obs_valid (line 12) | def _is_obs_valid(self, obs_spec, obs):
method _test_gym_obs_space (line 18) | def _test_gym_obs_space(self, env):
method test_env_without_noop (line 35) | def test_env_without_noop(self):
method test_env_with_noop (line 44) | def test_env_with_noop(self):
method test_env_with_fogmap (line 53) | def test_env_with_fogmap(self):
method test_system_disable (line 62) | def test_system_disable(self):
FILE: tests/core/test_map_generation.py
class TestMapGeneration (line 11) | class TestMapGeneration(unittest.TestCase):
method test_insufficient_maps (line 12) | def test_insufficient_maps(self):
method test_map_preview (line 35) | def test_map_preview(self):
method test_map_reset_from_fractal (line 55) | def test_map_reset_from_fractal(self):
FILE: tests/core/test_observation_tile.py
class TestObservationTile (line 23) | class TestObservationTile(unittest.TestCase):
method setUpClass (line 25) | def setUpClass(cls):
method test_tile_attr (line 32) | def test_tile_attr(self):
method test_action_target_consts (line 35) | def test_action_target_consts(self):
method test_obs_tile_correctness (line 40) | def test_obs_tile_correctness(self):
method test_env_visible_tiles_correctness (line 79) | def test_env_visible_tiles_correctness(self):
method test_make_attack_mask_within_range (line 112) | def test_make_attack_mask_within_range(self):
method test_gs_where_in_1d (line 144) | def test_gs_where_in_1d(self):
FILE: tests/core/test_tile_property.py
class TestTileProperty (line 10) | class TestTileProperty(unittest.TestCase):
method setUpClass (line 13) | def setUpClass(cls):
method test_fixed_habitability_passability (line 24) | def test_fixed_habitability_passability(self):
FILE: tests/core/test_tile_seize.py
class MockRealm (line 11) | class MockRealm:
method __init__ (line 12) | def __init__(self):
class MockTask (line 20) | class MockTask:
method __init__ (line 21) | def __init__(self, ent_id):
class MockEntity (line 24) | class MockEntity:
method __init__ (line 25) | def __init__(self, ent_id):
class TestTileSeize (line 31) | class TestTileSeize(unittest.TestCase):
method test_tile (line 33) | def test_tile(self):
method test_map_seize_targets (line 91) | def test_map_seize_targets(self):
FILE: tests/datastore/test_datastore.py
class TestDatastore (line 8) | class TestDatastore(unittest.TestCase):
method testdatastore_record (line 10) | def testdatastore_record(self):
FILE: tests/datastore/test_id_allocator.py
class TestIdAllocator (line 5) | class TestIdAllocator(unittest.TestCase):
method test_id_allocator (line 6) | def test_id_allocator(self):
method test_id_reuse (line 33) | def test_id_reuse(self):
FILE: tests/datastore/test_numpy_datastore.py
class TestNumpyTable (line 8) | class TestNumpyTable(unittest.TestCase):
method test_continous_table (line 9) | def test_continous_table(self):
method test_discrete_table (line 20) | def test_discrete_table(self):
method test_expand (line 31) | def test_expand(self):
FILE: tests/datastore/test_serialized.py
class MockDatastoreRecord (line 16) | class MockDatastoreRecord():
method __init__ (line 17) | def __init__(self):
method get (line 20) | def get(self, name):
method update (line 23) | def update(self, name, value):
class MockDatastore (line 26) | class MockDatastore():
method create_record (line 27) | def create_record(self, name):
method register_object_type (line 30) | def register_object_type(self, name, attributes):
class TestSerialized (line 34) | class TestSerialized(unittest.TestCase):
method test_serialized (line 36) | def test_serialized(self):
FILE: tests/systems/test_exchange.py
class MockRealm (line 13) | class MockRealm:
method __init__ (line 14) | def __init__(self):
class MockEntity (line 22) | class MockEntity:
method __init__ (line 23) | def __init__(self) -> None:
class TestExchange (line 30) | class TestExchange(unittest.TestCase):
method test_listings (line 31) | def test_listings(self):
method test_for_sale_items (line 70) | def test_for_sale_items(self):
FILE: tests/systems/test_item.py
class MockRealm (line 9) | class MockRealm:
method __init__ (line 10) | def __init__(self):
class TestItem (line 19) | class TestItem(unittest.TestCase):
method test_item (line 20) | def test_item(self):
method test_owned_by (line 56) | def test_owned_by(self):
FILE: tests/systems/test_skill_level.py
class TestSkillLevel (line 10) | class TestSkillLevel(unittest.TestCase):
method setUpClass (line 12) | def setUpClass(cls):
method test_experience_calculator (line 18) | def test_experience_calculator(self):
method test_add_xp (line 38) | def test_add_xp(self):
FILE: tests/task/test_demo_task_creation.py
function rollout (line 15) | def rollout(env, tasks, steps=5):
class TestDemoTask (line 21) | class TestDemoTask(unittest.TestCase):
method test_baseline_tasks (line 23) | def test_baseline_tasks(self):
method test_player_kill_reward (line 125) | def test_player_kill_reward(self):
method test_predicate_math (line 180) | def test_predicate_math(self):
method test_task_spec_based_curriculum (line 215) | def test_task_spec_based_curriculum(self):
FILE: tests/task/test_manual_curriculum.py
function PracticeFormation (line 76) | def PracticeFormation(gs, subject, dist, num_tick):
function PracticeInventoryManagement (line 174) | def PracticeInventoryManagement(gs, subject, space, num_tick):
function create_pool (line 305) | def create_pool(num_proc):
FILE: tests/task/test_predicates.py
class TestBasePredicate (line 28) | class TestBasePredicate(unittest.TestCase):
method _get_taskenv (line 31) | def _get_taskenv(self,
method _check_result (line 60) | def _check_result(self, env, test_preds, infos, true_task):
method _check_progress (line 76) | def _check_progress(self, task, infos, value):
method test_tickge_stay_alive_rip (line 83) | def test_tickge_stay_alive_rip(self):
method test_can_see_tile (line 141) | def test_can_see_tile(self):
method test_can_see_agent (line 185) | def test_can_see_agent(self):
method test_occupy_tile (line 226) | def test_occupy_tile(self):
method test_distance_traveled (line 259) | def test_distance_traveled(self):
method test_all_members_within_range (line 293) | def test_all_members_within_range(self):
method test_attain_skill (line 329) | def test_attain_skill(self):
method test_gain_experience (line 363) | def test_gain_experience(self):
method test_inventory_space_ge_not (line 397) | def test_inventory_space_ge_not(self):
method test_own_equip_item (line 430) | def test_own_equip_item(self):
method test_fully_armed (line 484) | def test_fully_armed(self):
method test_hoard_gold_and_team (line 516) | def test_hoard_gold_and_team(self): # HoardGold, TeamHoardGold
method test_exchange_gold_predicates (line 543) | def test_exchange_gold_predicates(self): # Earn Gold, Spend Gold, Make...
method test_count_event (line 580) | def test_count_event(self): # CountEvent
method test_score_hit (line 602) | def test_score_hit(self): # ScoreHit
method test_defeat_entity (line 643) | def test_defeat_entity(self): # PlayerKill
method test_item_event_predicates (line 698) | def test_item_event_predicates(self): # Consume, Harvest, List, Buy
FILE: tests/task/test_sample_task_from_file.py
class TestSampleTaskFromFile (line 6) | class TestSampleTaskFromFile(unittest.TestCase):
method test_sample_task_from_file (line 7) | def test_sample_task_from_file(self):
FILE: tests/task/test_task_api.py
function Success (line 24) | def Success(gs, subject: Group):
function Failure (line 27) | def Failure(gs, subject: Group):
function Fake (line 30) | def Fake(gs, subject, a,b,c):
class MockGameState (line 33) | class MockGameState():
method __init__ (line 34) | def __init__(self):
method clear_cache (line 41) | def clear_cache(self):
class TestTaskAPI (line 44) | class TestTaskAPI(unittest.TestCase):
method test_predicate_operators (line 45) | def test_predicate_operators(self):
method test_team_assignment (line 113) | def test_team_assignment(self):
method test_predicate_name (line 123) | def test_predicate_name(self):
method test_task_api_with_predicate (line 139) | def test_task_api_with_predicate(self):
method test_task_api_with_function (line 170) | def test_task_api_with_function(self):
method test_predicate_fn_using_other_predicate_fn (line 196) | def test_predicate_fn_using_other_predicate_fn(self):
method test_completed_tasks_in_info (line 283) | def test_completed_tasks_in_info(self):
method test_make_task_from_spec (line 331) | def test_make_task_from_spec(self):
method test_hold_duration_task (line 371) | def test_hold_duration_task(self):
method test_task_spec_with_predicate (line 426) | def test_task_spec_with_predicate(self):
FILE: tests/task/test_task_system_perf.py
class TestTaskSystemPerf (line 10) | class TestTaskSystemPerf(unittest.TestCase):
method test_nmmo_default_task (line 11) | def test_nmmo_default_task(self):
FILE: tests/test_death_fog.py
class TestDeathFog (line 6) | class TestDeathFog(unittest.TestCase):
method test_death_fog (line 7) | def test_death_fog(self):
FILE: tests/test_determinism.py
class TestDeterminism (line 16) | class TestDeterminism(unittest.TestCase):
method test_np_random_get_direction (line 17) | def test_np_random_get_direction(self):
method test_map_determinism (line 32) | def test_map_determinism(self):
method test_env_level_rng (line 59) | def test_env_level_rng(self):
FILE: tests/test_eventlog.py
class MockRealm (line 13) | class MockRealm:
method __init__ (line 14) | def __init__(self):
method step (line 23) | def step(self):
class MockEntity (line 27) | class MockEntity(Entity):
method __init__ (line 29) | def __init__(self, ent_id, **kwargs):
method ent_id (line 34) | def ent_id(self):
method attack_level (line 38) | def attack_level(self):
class TestEventLog (line 42) | class TestEventLog(unittest.TestCase):
method test_event_logging (line 44) | def test_event_logging(self):
FILE: tests/test_memory_usage.py
function test_memory_usage (line 6) | def test_memory_usage():
FILE: tests/test_mini_games.py
class TestMinigames (line 11) | class TestMinigames(unittest.TestCase):
method test_mini_games (line 12) | def test_mini_games(self):
FILE: tests/test_performance.py
function create_and_reset (line 20) | def create_and_reset(conf):
function create_config (line 24) | def create_config(base, *systems):
function benchmark_config (line 36) | def benchmark_config(benchmark, base, nent, *systems):
function test_small_env_creation (line 47) | def test_small_env_creation(benchmark):
function test_small_env_reset (line 50) | def test_small_env_reset(benchmark):
function test_fps_base_small_1_pop (line 56) | def test_fps_base_small_1_pop(benchmark):
function test_fps_minimal_small_1_pop (line 59) | def test_fps_minimal_small_1_pop(benchmark):
function test_fps_npc_small_1_pop (line 62) | def test_fps_npc_small_1_pop(benchmark):
function test_fps_test_small_1_pop (line 65) | def test_fps_test_small_1_pop(benchmark):
function test_fps_no_npc_small_1_pop (line 68) | def test_fps_no_npc_small_1_pop(benchmark):
function test_fps_all_small_1_pop (line 72) | def test_fps_all_small_1_pop(benchmark):
function test_fps_base_med_1_pop (line 75) | def test_fps_base_med_1_pop(benchmark):
function test_fps_minimal_med_1_pop (line 78) | def test_fps_minimal_med_1_pop(benchmark):
function test_fps_npc_med_1_pop (line 81) | def test_fps_npc_med_1_pop(benchmark):
function test_fps_test_med_1_pop (line 84) | def test_fps_test_med_1_pop(benchmark):
function test_fps_no_npc_med_1_pop (line 87) | def test_fps_no_npc_med_1_pop(benchmark):
function test_fps_all_med_1_pop (line 91) | def test_fps_all_med_1_pop(benchmark):
function test_fps_base_med_100_pop (line 94) | def test_fps_base_med_100_pop(benchmark):
function test_fps_minimal_med_100_pop (line 97) | def test_fps_minimal_med_100_pop(benchmark):
function test_fps_npc_med_100_pop (line 100) | def test_fps_npc_med_100_pop(benchmark):
function test_fps_test_med_100_pop (line 103) | def test_fps_test_med_100_pop(benchmark):
function test_fps_no_npc_med_100_pop (line 106) | def test_fps_no_npc_med_100_pop(benchmark):
function test_fps_all_med_100_pop (line 110) | def test_fps_all_med_100_pop(benchmark):
function set_seed_test (line 113) | def set_seed_test():
function set_seed_test_complex (line 131) | def set_seed_test_complex():
FILE: tests/test_pettingzoo.py
class TestPettingZoo (line 8) | class TestPettingZoo(unittest.TestCase):
method test_pettingzoo_api (line 9) | def test_pettingzoo_api(self):
FILE: tests/test_rollout.py
class SimpleConfig (line 4) | class SimpleConfig(nmmo.config.Small, nmmo.config.Combat):
function test_rollout (line 7) | def test_rollout():
FILE: tests/testhelpers.py
function actions_are_equal (line 18) | def actions_are_equal(source_atn, target_atn, debug=True):
function observations_are_equal (line 48) | def observations_are_equal(source_obs, target_obs, debug=True):
function player_total (line 85) | def player_total(env):
function count_actions (line 89) | def count_actions(tick, actions):
class ScriptedAgentTestConfig (line 110) | class ScriptedAgentTestConfig(nmmo.config.Small, nmmo.config.AllGameSyst...
class ScriptedAgentTestEnv (line 122) | class ScriptedAgentTestEnv(nmmo.Env):
method __init__ (line 129) | def __init__(self, config: nmmo.config.Config, seed=None):
method reset (line 139) | def reset(self, map_id=None, seed=None, options=None):
method _compute_scripted_agent_actions (line 143) | def _compute_scripted_agent_actions(self, actions):
function change_spawn_pos (line 164) | def change_spawn_pos(realm: Realm, ent_id: int, new_pos):
function provide_item (line 178) | def provide_item(realm: Realm, ent_id: int,
class ScriptedTestTemplate (line 186) | class ScriptedTestTemplate(unittest.TestCase):
method setUpClass (line 189) | def setUpClass(cls):
method _make_item_sig (line 211) | def _make_item_sig(self):
method _setup_env (line 221) | def _setup_env(self, random_seed, check_assert=True, remove_immunity=F...
method _check_ent_mask (line 264) | def _check_ent_mask(self, ent_obs, atn, target_id):
method _check_inv_mask (line 271) | def _check_inv_mask(self, ent_obs, atn, item_sig):
method _check_mkt_mask (line 279) | def _check_mkt_mask(self, ent_obs, item_id):
method _check_default_asserts (line 285) | def _check_default_asserts(self, env):
method _check_assert_make_action (line 330) | def _check_assert_make_action(self, env, atn, test_cond):
function profile_env_step (line 379) | def profile_env_step(action_target=True, tasks=None, condition=None):
Condensed preview — 121 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (628K chars).
[
{
"path": ".gitattributes",
"chars": 36,
"preview": "neural_mmo/_version.py export-subst\n"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 525,
"preview": "---\nname: Bug report\nabout: Report a bug with the Neural MMO environment or Unity3D Embyr client\ntitle: \"[Bug Report]\"\nl"
},
{
"path": ".github/ISSUE_TEMPLATE/documentation.md",
"chars": 506,
"preview": "---\nname: Documentation\nabout: Report problems with the documentation\ntitle: \"[Docs]\"\nlabels: ''\nassignees: ''\n\n---\n\nOne"
},
{
"path": ".github/ISSUE_TEMPLATE/enhancement.md",
"chars": 1343,
"preview": "---\nname: Enhancement\nabout: Suggest an improvement to an API or a refactorization of existing code for\n better efficie"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.md",
"chars": 834,
"preview": "---\nname: Feature request\nabout: Request a feature for the Neural MMO environment of Unity3D Embyr client\ntitle: \"[Featu"
},
{
"path": ".github/workflows/pylint-test.yml",
"chars": 1003,
"preview": "name: pylint-test\n\non: [push, pull_request]\n\njobs:\n build:\n runs-on: ubuntu-latest\n strategy:\n matrix:\n "
},
{
"path": ".gitignore",
"chars": 2926,
"preview": "# Game maps\nmaps/\n*.swp\nruns/*\nwandb/*\n\n# local replay file from test_render_save.py\ntests/replay_local*.pickle\nreplay*\n"
},
{
"path": ".pylintrc",
"chars": 1042,
"preview": "[MESSAGES CONTROL]\n\ndisable=W0511, # TODO/FIXME\n W0105, # string is used as a statement\n C0114, # missing "
},
{
"path": "LICENSE",
"chars": 1083,
"preview": "MIT License\n\nCopyright (c) 2019 OpenAI, 2020 Joseph Suarez\n\nPermission is hereby granted, free of charge, to any person "
},
{
"path": "MANIFEST.in",
"chars": 45,
"preview": "global-include *.pyx\ninclude nmmo/resource/*\n"
},
{
"path": "README.md",
"chars": 740,
"preview": "\n\n# :\n '''Base class for agents\n\n Args:\n con"
},
{
"path": "nmmo/core/config.py",
"chars": 24531,
"preview": "# pylint: disable=invalid-name\nfrom __future__ import annotations\n\nimport os\nimport sys\nimport logging\nimport re\n\nimport"
},
{
"path": "nmmo/core/env.py",
"chars": 21952,
"preview": "import os\nimport functools\nfrom typing import Any, Dict, List, Callable\nfrom collections import defaultdict\nfrom copy im"
},
{
"path": "nmmo/core/game_api.py",
"chars": 13118,
"preview": "# pylint: disable=no-member,bare-except\nfrom abc import ABC, abstractmethod\nfrom typing import Dict\nfrom collections imp"
},
{
"path": "nmmo/core/map.py",
"chars": 6749,
"preview": "from typing import List, Tuple\nimport numpy as np\nfrom ordered_set import OrderedSet\n\nfrom nmmo.core.tile import Tile\nfr"
},
{
"path": "nmmo/core/observation.py",
"chars": 22908,
"preview": "# pylint: disable=no-member,c-extension-no-member\nfrom functools import lru_cache\nimport numpy as np\n\nfrom nmmo.core.til"
},
{
"path": "nmmo/core/realm.py",
"chars": 7193,
"preview": "from __future__ import annotations\nfrom collections import defaultdict\nfrom typing import Dict\nimport numpy as np\n\nimpor"
},
{
"path": "nmmo/core/terrain.py",
"chars": 11373,
"preview": "import os\nimport logging\n\nimport numpy as np\nfrom imageio.v2 import imread, imsave\nfrom scipy import stats\n\nfrom nmmo.li"
},
{
"path": "nmmo/core/tile.py",
"chars": 3681,
"preview": "from types import SimpleNamespace\n\nfrom nmmo.datastore.serialized import SerializedState\nfrom nmmo.lib import material, "
},
{
"path": "nmmo/datastore/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "nmmo/datastore/datastore.py",
"chars": 2736,
"preview": "from __future__ import annotations\nfrom typing import Dict, List\nfrom nmmo.datastore.id_allocator import IdAllocator\n\n\"\""
},
{
"path": "nmmo/datastore/id_allocator.py",
"chars": 452,
"preview": "from ordered_set import OrderedSet\n\nclass IdAllocator:\n def __init__(self, max_id):\n # Key 0 is reserved as padding\n"
},
{
"path": "nmmo/datastore/numpy_datastore.py",
"chars": 2317,
"preview": "from typing import List\n\nimport numpy as np\n\nfrom nmmo.datastore.datastore import Datastore, DataTable\n\n\nclass NumpyTabl"
},
{
"path": "nmmo/datastore/serialized.py",
"chars": 3764,
"preview": "# pylint: disable=bare-except,c-extension-no-member\nfrom __future__ import annotations\nfrom ast import Tuple\n\nimport mat"
},
{
"path": "nmmo/entity/__init__.py",
"chars": 76,
"preview": "from nmmo.entity.entity import Entity\nfrom nmmo.entity.player import Player\n"
},
{
"path": "nmmo/entity/entity.py",
"chars": 11197,
"preview": "import math\nfrom types import SimpleNamespace\nimport numpy as np\n\nfrom nmmo.datastore.serialized import SerializedState\n"
},
{
"path": "nmmo/entity/entity_manager.py",
"chars": 4218,
"preview": "from collections.abc import Mapping\nfrom typing import Dict\n\nfrom nmmo.entity.entity import Entity, EntityState\nfrom nmm"
},
{
"path": "nmmo/entity/npc.py",
"chars": 11370,
"preview": "import numpy as np\nfrom nmmo.entity import entity\nfrom nmmo.core import action as Action\nfrom nmmo.systems import combat"
},
{
"path": "nmmo/entity/npc_manager.py",
"chars": 3194,
"preview": "from typing import Callable\nfrom nmmo.entity.entity_manager import EntityGroup\nfrom nmmo.entity.npc import NPC, Soldier,"
},
{
"path": "nmmo/entity/player.py",
"chars": 6802,
"preview": "from nmmo.systems.skill import Skills\nfrom nmmo.entity import entity\nfrom nmmo.lib.event_code import EventCode\nfrom nmmo"
},
{
"path": "nmmo/lib/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "nmmo/lib/astar.py",
"chars": 1899,
"preview": "#pylint: disable=invalid-name\nimport heapq\nfrom nmmo.lib.utils import in_bounds\n\nCUTOFF = 100\n\ndef l1(start, goal):\n sr"
},
{
"path": "nmmo/lib/colors.py",
"chars": 4180,
"preview": "# pylint: disable=all\n\n#Various Enums used for handling materials, entity types, etc.\n#Data texture pairs are used for e"
},
{
"path": "nmmo/lib/cython_helper.pyx",
"chars": 2134,
"preview": "#cython: boundscheck=True\n#cython: wraparound=True\n#cython: nonecheck=True\n\nfrom types import SimpleNamespace\nimport num"
},
{
"path": "nmmo/lib/event_code.py",
"chars": 538,
"preview": "class EventCode:\n # Move\n EAT_FOOD = 1\n DRINK_WATER = 2\n GO_FARTHEST = 3 # record when breaking the previous record"
},
{
"path": "nmmo/lib/event_log.py",
"chars": 9254,
"preview": "from types import SimpleNamespace\nfrom typing import List\nfrom copy import deepcopy\nfrom collections import defaultdict\n"
},
{
"path": "nmmo/lib/material.py",
"chars": 4555,
"preview": "\nfrom nmmo.systems import item, droptable\n\nclass Material:\n capacity = 0\n tool = None\n table = None\n index = None\n "
},
{
"path": "nmmo/lib/seeding.py",
"chars": 1349,
"preview": "# copied from https://github.com/openai/gym/blob/master/gym/utils/seeding.py\n\"\"\"Set of random number generator functions"
},
{
"path": "nmmo/lib/spawn.py",
"chars": 2244,
"preview": "from itertools import chain\n\nclass SequentialLoader:\n '''config.PLAYER_LOADER that spreads out agent populations'''\n d"
},
{
"path": "nmmo/lib/team_helper.py",
"chars": 5467,
"preview": "from typing import Any, Dict, List\nimport numpy.random\nfrom nmmo.lib import spawn\n\n\ndef make_teams(config, num_teams):\n "
},
{
"path": "nmmo/lib/utils.py",
"chars": 3973,
"preview": "# pylint: disable=all\nimport inspect\nfrom collections import deque\nimport hashlib\nimport numpy as np\nfrom nmmo.entity.en"
},
{
"path": "nmmo/lib/vec_noise.py",
"chars": 2963,
"preview": "import numpy as np\n\n# The noise2() was ported from https://github.com/zbenjamin/vec_noise\n\n# https://github.com/zbenjami"
},
{
"path": "nmmo/minigames/__init__.py",
"chars": 286,
"preview": "from .center_race import RacetoCenter, ProgressTowardCenter\nfrom .king_hill import KingoftheHill\nfrom .sandwich import S"
},
{
"path": "nmmo/minigames/center_race.py",
"chars": 5660,
"preview": "# pylint: disable=invalid-name, duplicate-code, unused-argument\nimport time\nfrom nmmo.core.game_api import Game\nfrom nmm"
},
{
"path": "nmmo/minigames/comm_together.py",
"chars": 6379,
"preview": "# pylint: disable=duplicate-code, invalid-name, unused-argument\nimport time\nfrom nmmo.core.game_api import TeamBattle\nfr"
},
{
"path": "nmmo/minigames/king_hill.py",
"chars": 6608,
"preview": "# pylint: disable=invalid-name, duplicate-code, unused-argument\nimport time\nfrom nmmo.core.game_api import TeamBattle\nfr"
},
{
"path": "nmmo/minigames/radio_raid.py",
"chars": 8591,
"preview": "# pylint: disable=duplicate-code, invalid-name, unused-argument\nimport time\nfrom nmmo.core.game_api import TeamBattle\nfr"
},
{
"path": "nmmo/minigames/sandwich.py",
"chars": 7944,
"preview": "import time\nimport numpy as np\n\nfrom nmmo.core.game_api import TeamBattle, team_survival_task\nfrom nmmo.task import task"
},
{
"path": "nmmo/render/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "nmmo/render/overlay.py",
"chars": 4385,
"preview": "import numpy as np\n\nfrom nmmo.lib.colors import Neon\nfrom nmmo.systems import combat\n\nfrom .render_utils import normaliz"
},
{
"path": "nmmo/render/render_client.py",
"chars": 2796,
"preview": "from __future__ import annotations\nimport numpy as np\n\nfrom nmmo.render.overlay import OverlayRegistry\nfrom nmmo.render."
},
{
"path": "nmmo/render/render_utils.py",
"chars": 2634,
"preview": "import numpy as np\nfrom scipy import signal\n\nfrom nmmo.lib.colors import Neon\n\n# NOTE: added to fix json.dumps() cannot "
},
{
"path": "nmmo/systems/__init__.py",
"chars": 25,
"preview": "from .skill import Skill\n"
},
{
"path": "nmmo/systems/combat.py",
"chars": 4595,
"preview": "#Various utilities for managing combat, including hit/damage\n\nimport numpy as np\n\nfrom nmmo.systems import skill as Skil"
},
{
"path": "nmmo/systems/droptable.py",
"chars": 1206,
"preview": "class Fixed():\n def __init__(self, item):\n self.item = item\n\n def roll(self, realm, level):\n return [self.item(r"
},
{
"path": "nmmo/systems/exchange.py",
"chars": 5980,
"preview": "from __future__ import annotations\nfrom collections import deque\nimport math\n\nfrom typing import Dict\n\nfrom nmmo.systems"
},
{
"path": "nmmo/systems/inventory.py",
"chars": 5600,
"preview": "from typing import Dict, Tuple\n\nfrom ordered_set import OrderedSet\n\nfrom nmmo.systems import item as Item\nclass Equipmen"
},
{
"path": "nmmo/systems/item.py",
"chars": 12302,
"preview": "from __future__ import annotations\n\nimport math\nfrom abc import ABC\nfrom types import SimpleNamespace\nfrom typing import"
},
{
"path": "nmmo/systems/skill.py",
"chars": 10163,
"preview": "from __future__ import annotations\n\nimport abc\n\nimport numpy as np\nfrom ordered_set import OrderedSet\n\nfrom nmmo.lib imp"
},
{
"path": "nmmo/task/__init__.py",
"chars": 79,
"preview": "from .game_state import *\nfrom .predicate_api import *\nfrom .task_api import *\n"
},
{
"path": "nmmo/task/base_predicates.py",
"chars": 13858,
"preview": "#pylint: disable=invalid-name, unused-argument, no-value-for-parameter\nfrom __future__ import annotations\nfrom typing im"
},
{
"path": "nmmo/task/game_state.py",
"chars": 9623,
"preview": "from __future__ import annotations\nfrom typing import Dict, Iterable, Tuple, MutableMapping, Set, List\nfrom dataclasses "
},
{
"path": "nmmo/task/group.py",
"chars": 2916,
"preview": "from __future__ import annotations\nfrom typing import Dict, Union, Iterable, TYPE_CHECKING\nfrom collections import Order"
},
{
"path": "nmmo/task/predicate_api.py",
"chars": 9763,
"preview": "from __future__ import annotations\nfrom typing import Callable, List, Optional, Union, Iterable, Type, TYPE_CHECKING\nfro"
},
{
"path": "nmmo/task/task_api.py",
"chars": 8408,
"preview": "# pylint: disable=unused-import,attribute-defined-outside-init\nfrom typing import Callable, Iterable, Dict, List, Union,"
},
{
"path": "nmmo/task/task_spec.py",
"chars": 6767,
"preview": "import functools\nfrom dataclasses import dataclass, field\nfrom typing import Iterable, Dict, List, Union, Type\nfrom type"
},
{
"path": "nmmo/version.py",
"chars": 22,
"preview": "__version__ = '2.1.2'\n"
},
{
"path": "pyproject.toml",
"chars": 76,
"preview": "[build-system]\nrequires = [\"setuptools\", \"wheel\", \"cython\", \"numpy==1.23.3\"]"
},
{
"path": "scripted/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "scripted/attack.py",
"chars": 1229,
"preview": "# pylint: disable=invalid-name, unused-argument\nimport numpy as np\n\nimport nmmo\nfrom nmmo.core.observation import Observ"
},
{
"path": "scripted/baselines.py",
"chars": 15531,
"preview": "# pylint: disable=invalid-name, attribute-defined-outside-init, no-member\nfrom typing import Dict\nfrom collections impor"
},
{
"path": "scripted/move.py",
"chars": 7422,
"preview": "# pylint: disable=invalid-name, unused-argument\nimport heapq\nimport numpy as np\n\nfrom nmmo.core import action\nfrom nmmo."
},
{
"path": "setup.py",
"chars": 1994,
"preview": "from itertools import chain\n\nfrom setuptools import find_packages, setup\nfrom Cython.Build import cythonize\nimport numpy"
},
{
"path": "tests/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/action/test_ammo_use.py",
"chars": 17614,
"preview": "import unittest\nimport logging\nimport numpy as np\n\nfrom tests.testhelpers import ScriptedTestTemplate, provide_item\n\nfro"
},
{
"path": "tests/action/test_destroy_give_gold.py",
"chars": 11215,
"preview": "import unittest\nimport logging\n\nfrom tests.testhelpers import ScriptedTestTemplate, change_spawn_pos, provide_item\n\nfrom"
},
{
"path": "tests/action/test_monkey_action.py",
"chars": 3072,
"preview": "import unittest\nimport random\nfrom tqdm import tqdm\n\nimport numpy as np\n\nfrom tests.testhelpers import ScriptedAgentTest"
},
{
"path": "tests/action/test_sell_buy.py",
"chars": 7049,
"preview": "import unittest\nimport logging\n\nfrom tests.testhelpers import ScriptedTestTemplate, provide_item\n\nfrom nmmo.core import "
},
{
"path": "tests/conftest.py",
"chars": 399,
"preview": "\n#pylint: disable=unused-argument\n\nimport logging\nlogging.basicConfig(level=logging.INFO, stream=None)\n\ndef pytest_bench"
},
{
"path": "tests/core/test_config.py",
"chars": 1027,
"preview": "import unittest\n\nimport nmmo\nimport nmmo.core.config as cfg\n\n\nclass Config(cfg.Config, cfg.Terrain, cfg.Combat):\n pass\n"
},
{
"path": "tests/core/test_cython_masks.py",
"chars": 2469,
"preview": "# pylint: disable=protected-access,bad-builtin\nimport unittest\nfrom timeit import timeit\nfrom copy import deepcopy\n#impo"
},
{
"path": "tests/core/test_entity.py",
"chars": 3462,
"preview": "import unittest\nimport numpy as np\n\nimport nmmo\nfrom nmmo.entity.entity import Entity, EntityState\nfrom nmmo.datastore.n"
},
{
"path": "tests/core/test_env.py",
"chars": 7424,
"preview": "import unittest\nfrom typing import List\n\nimport random\nimport numpy as np\nfrom tqdm import tqdm\n\nimport nmmo\nfrom nmmo.c"
},
{
"path": "tests/core/test_game_api.py",
"chars": 4991,
"preview": "# pylint: disable=protected-access\nimport unittest\n\nimport nmmo\nfrom nmmo.core.game_api import AgentTraining, TeamTraini"
},
{
"path": "tests/core/test_gym_obs_spaces.py",
"chars": 3220,
"preview": "import unittest\nfrom copy import deepcopy\nimport numpy as np\n\nimport nmmo\nfrom nmmo.core.game_api import DefaultGame\n\nRA"
},
{
"path": "tests/core/test_map_generation.py",
"chars": 5318,
"preview": "# pylint: disable=protected-access\nimport unittest\nimport os\nimport shutil\nimport numpy as np\n\nimport nmmo\nfrom nmmo.lib"
},
{
"path": "tests/core/test_observation_tile.py",
"chars": 8592,
"preview": "# pylint: disable=protected-access,bad-builtin\nimport unittest\nfrom timeit import timeit\nfrom collections import default"
},
{
"path": "tests/core/test_tile_property.py",
"chars": 1134,
"preview": "import unittest\n\nimport copy\nimport nmmo\nfrom scripted.baselines import Sleeper\n\nHORIZON = 32\n\n\nclass TestTileProperty(u"
},
{
"path": "tests/core/test_tile_seize.py",
"chars": 4408,
"preview": "# pylint: disable=protected-access\nimport unittest\nimport numpy as np\n\nimport nmmo\nimport nmmo.core.map\nfrom nmmo.core.t"
},
{
"path": "tests/datastore/test_datastore.py",
"chars": 1077,
"preview": "import unittest\n\nimport numpy as np\n\nfrom nmmo.datastore.numpy_datastore import NumpyDatastore\n\n\nclass TestDatastore(uni"
},
{
"path": "tests/datastore/test_id_allocator.py",
"chars": 1576,
"preview": "import unittest\n\nfrom nmmo.datastore.id_allocator import IdAllocator\n\nclass TestIdAllocator(unittest.TestCase):\n def te"
},
{
"path": "tests/datastore/test_numpy_datastore.py",
"chars": 1216,
"preview": "import unittest\n\nimport numpy as np\n\nfrom nmmo.datastore.numpy_datastore import NumpyTable\n\n# pylint: disable=protected-"
},
{
"path": "tests/datastore/test_serialized.py",
"chars": 1362,
"preview": "from collections import defaultdict\nimport unittest\n\nfrom nmmo.datastore.serialized import SerializedState\n\n# pylint: di"
},
{
"path": "tests/render/test_load_replay.py",
"chars": 507,
"preview": "'''Manual test for rendering replay'''\n\nif __name__ == '__main__':\n import time\n\n # pylint: disable=import-error\n fro"
},
{
"path": "tests/render/test_render_save.py",
"chars": 2544,
"preview": "# Deprecated test; old render system\n\n'''Manual test for render client connectivity and save replay\nimport nmmo\nfrom nmm"
},
{
"path": "tests/systems/test_exchange.py",
"chars": 2942,
"preview": "# pylint: disable=unnecessary-lambda,protected-access,no-member\nfrom types import SimpleNamespace\nimport unittest\nimport"
},
{
"path": "tests/systems/test_item.py",
"chars": 2428,
"preview": "import unittest\nimport numpy as np\n\nimport nmmo\nfrom nmmo.datastore.numpy_datastore import NumpyDatastore\nfrom nmmo.syst"
},
{
"path": "tests/systems/test_skill_level.py",
"chars": 3265,
"preview": "import unittest\n\nimport numpy as np\n\nimport nmmo\nimport nmmo.systems.skill\nfrom tests.testhelpers import ScriptedAgentTe"
},
{
"path": "tests/task/test_demo_task_creation.py",
"chars": 9996,
"preview": "# pylint: disable=invalid-name,unused-argument,unused-variable\nimport unittest\nfrom tests.testhelpers import ScriptedAge"
},
{
"path": "tests/task/test_manual_curriculum.py",
"chars": 15032,
"preview": "'''Manual test for creating learning curriculum manually'''\n# pylint: disable=invalid-name,redefined-outer-name,bad-buil"
},
{
"path": "tests/task/test_predicates.py",
"chars": 31126,
"preview": "import unittest\nfrom typing import List, Tuple, Union, Iterable\nimport random\n\nfrom tests.testhelpers import ScriptedAge"
},
{
"path": "tests/task/test_sample_task_from_file.py",
"chars": 759,
"preview": "import unittest\n\nimport nmmo\nfrom tests.testhelpers import ScriptedAgentTestConfig\n\nclass TestSampleTaskFromFile(unittes"
},
{
"path": "tests/task/test_task_api.py",
"chars": 18763,
"preview": "# pylint: disable=unused-argument,invalid-name\nimport unittest\nfrom types import FunctionType\nimport numpy as np\n\nimport"
},
{
"path": "tests/task/test_task_system_perf.py",
"chars": 2654,
"preview": "import unittest\n\nimport nmmo\nfrom nmmo.core.env import Env\nfrom nmmo.task.task_api import Task, nmmo_default_task\nfrom t"
},
{
"path": "tests/test_death_fog.py",
"chars": 1612,
"preview": "# pylint: disable=protected-access, no-member\nimport unittest\nimport nmmo\n\n\nclass TestDeathFog(unittest.TestCase):\n def"
},
{
"path": "tests/test_determinism.py",
"chars": 3831,
"preview": "import unittest\nfrom timeit import timeit\nimport numpy as np\nfrom tqdm import tqdm\n\nimport nmmo\nfrom nmmo.lib import see"
},
{
"path": "tests/test_eventlog.py",
"chars": 5095,
"preview": "import unittest\n\nimport nmmo\nfrom nmmo.datastore.numpy_datastore import NumpyDatastore\nfrom nmmo.lib.event_log import Ev"
},
{
"path": "tests/test_memory_usage.py",
"chars": 243,
"preview": "# pylint: disable=bad-builtin, unused-variable\nimport psutil\n\nimport nmmo\n\ndef test_memory_usage():\n env = nmmo.Env()\n "
},
{
"path": "tests/test_mini_games.py",
"chars": 927,
"preview": "# pylint: disable=protected-access\nimport unittest\nimport numpy as np\nimport nmmo\nfrom nmmo import minigames as mg\nfrom "
},
{
"path": "tests/test_performance.py",
"chars": 6059,
"preview": "# pylint: disable=no-member\n# import time\nimport cProfile\nimport io\nimport pstats\nfrom tqdm import tqdm\n\nimport nmmo\nfro"
},
{
"path": "tests/test_pettingzoo.py",
"chars": 410,
"preview": "import unittest\nfrom pettingzoo.test import parallel_api_test\n\nimport nmmo\nfrom scripted import baselines\n\n\nclass TestPe"
},
{
"path": "tests/test_rollout.py",
"chars": 395,
"preview": "import nmmo\nfrom scripted.baselines import Random\n\nclass SimpleConfig(nmmo.config.Small, nmmo.config.Combat):\n pass\n\nde"
},
{
"path": "tests/testhelpers.py",
"chars": 13964,
"preview": "import logging\nimport unittest\n\nfrom copy import deepcopy\nfrom timeit import timeit\nimport numpy as np\n\nimport nmmo\nfrom"
},
{
"path": "utils/git-pr.sh",
"chars": 1812,
"preview": "#!/bin/bash\nMASTER_BRANCH=\"2.0\"\n\n# check if in master branch\ncurrent_branch=$(git rev-parse --abbrev-ref HEAD)\nif [ \"$cu"
},
{
"path": "utils/pre-git-check.sh",
"chars": 1447,
"preview": "#!/bin/bash\n\necho\necho \"Checking pylint, xcxc, pytest without touching git\"\necho\n\n# Check the number of physical cores o"
},
{
"path": "utils/run-perf-tests.sh",
"chars": 217,
"preview": "pytest --benchmark-columns=ops,rounds,median,mean,stddev,min,max,iterations --benchmark-max-time=5 --benchmark-min-round"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the jsuarez5341/neural-mmo GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 121 files (586.7 KB), approximately 159.8k tokens, and a symbol index with 1447 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.