pryaxis / orion-core Goto Github PK
View Code? Open in Web Editor NEWThe next generation Terraria Server API.
Home Page: https://pryaxis.github.io/orion-core
License: GNU General Public License v3.0
The next generation Terraria Server API.
Home Page: https://pryaxis.github.io/orion-core
License: GNU General Public License v3.0
Until now, Orion only allows service definitions internal to its own assembly. They are defined by us and essentially hard-coded. The injector doesn't know how to bind interfaces it doesn't know about.
It's time to have a discussion about extensibility to the service mechanism which will allow the flexibility to create "foreign" service types externally, from assemblies in the plugins
directory in the same way plugins are harvested.
As the author of SEconomy, a plugin that provides a facility (or service) to other plugins and extensions, I should have the ability to define my own services in which will be injected into my own plugin, and others.
Consider a hypothetical SEconomy service definition externally in Orion:
public interface IWorldEconomyService : IService
{
}
In this design, I define my own world economy service to Orion which is contained in my plugin assembly. The assembly has its own default implementation which SEconomy will use, but other people may choose to override the service and provide their own world economy.
As long as they implement IWorldEconomyService
, there is nothing stoping plugins from extending other plugins' functionality.
Consider a hypothetical SEconomy plugin:
public class EXPPlugin(Orion orion, ICurrency<ExpCurrency> currencyService,
IWorldEconomyService worldEconomy) : base(orion)
{
...
}
In this example, IWorldEconomyService
by default shall point to its own internal implementation of world economy, however doesn't have to. The same semantics shall apply to ICurrency<>
, or any service definition.
In OTAPI we expect there to be missing and/or differing hooks. These will need to be implemented in order to not lose features in the shift to Orion
This is a project I discussed with @tylerjwatson. Effectively, I'm going through the entire TShock 4 codebase and identifying key features, commands, subsystems, etc. and dividing them up in a table to determine where they should go in TShock 5 and Orion.
Because the wiki doesn't have any way to send notifications to Slack, I'm creating an issue here to track my progress as I work to enumerate through features. In addition, I plan on asking for input directly when something comes into question (should feature X or Y be in Orion or in TShock 5?).
Very spare, but I hope to make significant progress in the next week or so to help guide implementation of features in Orion that need to be done before TShock 5 can start being designed around Orion.
TShock 4 Feature | In Orion? | Service def. in Orion? | In TShock 5? | Status / Comment |
---|---|---|---|---|
Rest API | ✔︎ | ✔︎ | Not complete. See #6. Many routes will be added in both TShock 5 and Orion, but the core implementation will be in Orion. TShock will add routes as necessary (and the services it implements will too). | |
Log system | ✔︎ | Not started. Presumably logging will be handled by an Orion service and TShock will deliver its messages to that service. See #19. Almost all log related config options should be removed from TShock, except maybe a binary "send logs to Orion log service" option. | ||
Bans | ✔︎ | ✔︎ | To be determined. Not sure if this is supposed to be in AAA or not. If in AAA, not sure if it's supposed to handle user facing entry points or if another plugin should be exposing AAA. | |
Warps | ✔︎ | Not migrated. Warp subsystem likely handled entirely in TShock 5, but could be removed and split into another plugin. | ||
Regions | ✔︎ | Not migrated. Region subsystem likely handled entirely in TShock 5, but could be removed and split into another plugin. | ||
Backups | ? | ? | ? | Not sure if this subsystem should be in TShock 5, but will likely remain in TShock 5. |
Groups | ✔︎ | ✔︎ | Not completed. Should be implemented in AAA, but unclear if user facing entry points should be in Orion or if TShock should implement them (commands, for example). | |
Users | ✔︎ | ✔︎ | Not completed. Should be implemented in AAA, but unclear if user facing entry points should be in Orion or if TShock should implement them (commands, for example). | |
Item bans | ✔︎ | Not migrated. Item bans subsystem likely handled entirely in TShock 5, but could be removed and split into another plugin. | ||
Projectile bans | ✔︎ | Not migrated. Projectile ban subsystem likely handled entirely in TShock 5, but could be removed and split into another plugin. | ||
Tile bans | ✔︎ | Not migrated. Tile ban subsystem likely handled entirely in TShock 5, but could be removed and split into another plugin. | ||
Remembered position manager | ✔︎ | Not migrated. Remembered pos manager likely handled in TShock 5, but should probably be moved into another plugin. | ||
Server side characters | ✔︎ | Not migrated. SSC likely handled in TShock 5. Could be a separate plugin, but also really hard to do and really easy to screw up. | ||
Stat tracker | ✔︎ | Not started. Likely handled in Orion as a service. To be completely removed in TShock 5. | ||
GeoIP | ✔︎ | Not migrated. Likely handled in TShock 5. Could be a separate plugin, but recent rewrites by Enerdy actually have it working. | ||
Database | ? | ? | Not clear as to where plugins can store arbitrary data. | |
Whitelist | ✔︎ | Not migrated. Likely handled in TShock 5. | ||
Config | ? | ? | ? | Unclear where arbitrary config files are read/written and how this correlates in TShock 5. |
Misc random config file settings in TShock | ✔︎ | Not migrated. Most will make it into TShock 5 or be split out into plugins. |
TShock's current database system for users currently lacks keys and aren't relational. Key identifying credentials such as IP addresses and UUIDs are hacked on in JSON/text columns and aren't useful for a proper SQL relational db.
Many-to-many with [UUID] means that one user may have many UUIDs, and may log in to any account they have been previously bound with by using /login accountname
, and UUID reporting becomes very easy, you can now query the database for every [User] that has ever logged in under a specified [UUID].
Many-to-many with [IPAddress] follows the same pattern. It's easy to report on any IP address who has connected to the server, and along with any TShock account they have touched. Conversely, you can report on any IP address who has touched a single TShock account.
Moving to a modular system in orion paves the way for transparent configuration, so we may as well use it. Configuration is a pain point for current plugin developers.
The proposed architecture makes it possible for plugins to store their configs in a clean and consistent fashion, without the developer ever having to care about the physical files themselves; all they need to care about is their config class in their code is full of human configured values.
I propose that configuration management be perfomed by Orion itself, and not by plugins. If an Orion module is to have a configuration, it must provide a configuation class inheriting from OrionModuleConfiguration
, otherwise no configuration for the module is assumed.
If the configuration doesn't exist, Orion is to create one via the interface, with default values in the class.
This class is to contain regular properties full of arrays and everything that the module needs to configure itself from a human perspective, and Orion fetches configurations during its init/reload phase and provides an instance of the deserialized configuration class to the instance via the OrionModuleBase
constructor which could have a OrionModuleConfiguraion config
parameter. Alternatively, OrionModuleBase
could have a virtual GetConfiguration<TConfiguration>()
where TConfiguration is the type of class the module has registered.
OrionModuleConfigurationBase
is to haveSave()
, Reload()
and any other sensible methods which will tell orion to manipulate config.
Configuration is to come from an IConfigurationProvider
framework implementation discussed in #12. It may write to JSON, it may write to XML, it may write to SQL, it may write to REST. it could, in effect, be anything.
TSAPI shall be modified, with the old Environment.CurrentDirectory
assuming code removed in lieu of a smarter system, particularly for Linux servers which expect configuration to be in /etc/
. There must also be a shared plugins directory to help shared hosting providers run only one copy of plugin files.
TBD
Orion plugins only have to write a class full of properties for configuration, and Orion takes care of the rest.
TODO
Speaks for itself, ChatAboveHeads just as in TShock.
Authentication, Authorization and Accounting service, which takes care of user accounts, authroization, groups, and permissions.
Define and implement IProjectile
and IProjectileService
abstractions. Implement projectile-related events, as well.
Our event infrastructure should have the following characteristics:
If the stat tracker is reimplemented in Orion it may need to include support for an opt-out: Pryaxis/TShock#936
This issue will serve as a tracker for Orion progress (under the v1.4 branch). It will also indicate how "ready" Orion is in terms of developing against it. A large portion of the pre-development milestones have been knocked out, which means that it is pretty much okay to start developing against Orion.
The concurrent development milestones can be completed at the same time as developing against Orion, as it involves supplementing Orion with missing packet definitions, event definitions, etc.
Pre-development will be done mostly directly onto the v1.4 branch. Once there is enough developer bandwidth (besides me), we can begin migrating to a better workflow involving new branches and pull requests.
ILogger
and Lazy<T>
OrionKernel
struct
-based packetsstruct
-based modulesOrionPlayerService
Implement abstractions for Terraria tile entities.
C# Scripting is now feasibly possible with the Roslyn Compiler Platform. It could be possible to allow for plugins to be loaded in the form of C# source code files and have them dynamically compiled to assemblies at run-time. This could possibly allow for hot-reloading and runtime edits by removing references to the hot-potato code and recompiling it.
This is mentioned by @Patrikkk in #35 during our discussion about plugin hot-reloading. A separate discussion about a scripting engine vs. a pre-compiled plugin engine is worth having. Please funnel all scripting engine discussion to this issue.
The purpose of this proposal is to outline the requirements for server behaviour in regards to POSIX directory structure support and RFC for such features.
The server should move away from the implicit current directory base structure in favour of a configurable base directory that may be globally based, or overridden to support instances of TShock 5.0 on the same server.
$ORION_BASE_PATH
/modules$ORION_BASE_PATH/config
. The path in which configuration resides is transparently handled by the OrionConfiguration
module and isn't needed to be worried about by plugins themselves.ORION_BASE_PATH
which specifies an absolute base directory in which to bootstrap itself out of. This method is easily supported by systemd, launchctl and upstart, and allows for one server per host and server instances at the same time
/usr/bin/
- binary directory/etc/orion/
- base system-wide configuration/var/lib/orion
- base system-wide orion data/var/lib/orion/modules
- base system-wide plugin directory/var/lib/orion/data
- base system-wide database directory/usr/bin/
- binary directory/Library/Frameworks/Orion.framework/
- base system-wide orion data/Library/Frameworks/Orion.framework/Modules
- base system-wide configuration%ProgramFiles%\Orion
- base binary directory
Note: Most of this is TBD because I don't understand SSC mechanisms yet
TShock's SSC mechanism is largely unknown, but in the very least we should look to save people's items in [UserInventory] and [UserInventorySet] tables.
Grouping sets of people's inventories will allow keeping a history of peoples items. For example, a plugin may choose to 'snapshot' an inventory which are saved as an [InventorySet], or a plugin may create an arbitrary [InventorySet] which may be applied to any account at any point in the game.
TBD
Up until now there is only support for one type of service, IService
which lives in singleton scope. It has been identified that not all services are fit for a singleton, so two service types must be created.
There is no reason for IConfigurationService
to be singleton. It would be better served as a generic service that takes a type argument for the class that it is to be serializing, to enable multiples of them to be injected into a target.
Consider the following hypothetical plugin:
public class MyPlugin : OrionPlugin {
public MyPlugin(Orion orion, IConfigurationService<GlobalConfiguration> globalConfig,
IConfigurationService<WorldConfiguration> worldConfig) : base(orion)
{
}
}
In this example, GlobalConfiguration
and WorldConfiguration
are classes which hold properties for two different configuration elements in a single plugin, and are best stored in two separate files. Many plugins traditionally use more than one type of configuration so this design is pertinent.
The best use case for non-singleton services would enable the serialization and de-serialization of separate configuration classes, one per service instance. This would require two types of services to be handled by Orion's injection mechanism
Orion at the moment seems to be largely a copy-paste of TShock's design patterns. Not to discredit the design as you have to start somewhere, thinking about the architecture I would really like to initiate the change to a modular design.
It's vital that chunks of Orion be self-contained, as they may be swapped out for anything else.
I propose that modules in Orion be self-contained classes inheriting from an abstract OrionModuleBase
class which essentially just defines a single Run()
method which the core will call to run it. OrionModuleBase implements IDisposable
, and the module must be responsible for destroying its own functionality, and the server must be able to run without it enabled; it just misses that functionality:
core.Get<TModule>()
method which will return the instance of module for that type, beware of nulls.[OrionModule("ModuleName", "Author", {order})]
attribute, and instantiated when it runs.OrionModuleBase.Run()
methodWe're getting a security alert for this CVE: https://nvd.nist.gov/vuln/detail/CVE-2018-1000210
The patch seems to be a trivial update to packages.config
:
<package id="YamlDotNet" version="5.0.0" />
This is a high priority issue because we're going to be emailed about it until it gets fixed. Coincidentally, this is also a great hacktoberfest issue!
Hello Hacktoberfesters! If you're looking for something to do please patch this. It's really easy.
Over the years we have had many requests for TShock to be translated, as well as offers from translators to do so. However, due to the nature of the codebase (i.e., hardcoded strings), this is a difficult task.
I propose we make use of Visual Studio's built in resx file support (read more here) to help remedy this issue.
Provide definitions for each Terraria packet (names are WIP):
ClientConnectPacket
ServerDisconnectPacket
ServerIndexPacket
PlayerDataPacket
PlayerInventoryPacket
PlayerJoinPacket
WorldInfoPacket
SectionRequestPacket
ClientStatusPacket
SectionInfoPacket
SectionFramesPacket
PlayerSpawnPacket
PlayerInfoPacket
PlayerActivityPacket
PlayerHealthPacket
TileModifyPacket
WorldTimePacket
DoorTogglePacket
TileSquarePacket
ItemInfoPacket
ItemOwnerPacket
NpcInfoPacket
ProjectileInfoPacket
NpcDamagePacket
ProjectileRemovePacket
PlayerPvpPacket
ChestOpenPacket
ChestInventoryPacket
ChestInfoPacket
ChestModifyPacket
PlayerHealthEffectPacket
PlayerZonesPacket
ServerPasswordedPacket
ClientPasswordPacket
ItemDisownPacket
PlayerTownNpcPacket
PlayerAnimationPacket
PlayerManaPacket
PlayerManaEffectPacket
PlayerTeamPacket
SignReadPacket
SignInfoPacket
TileLiquidPacket
PlayerEnterPacket
PlayerBuffsPacket
EntityEffectPacket
ObjectUnlockPacket
NpcBuffPacket
NpcBuffsPacket
PlayerBuffPacket
NpcNamePacket
WorldBiomesPacket
PlayerMusicPacket
WireActivatePacket
NpcHomePacket
WorldSummonPacket
PlayerDodgePacket
BlockPaintPacket
WallPaintPacket
EntityTeleportPacket
PlayerHealPacket
ClientUuidPacket
ChestNamePacket
NpcCatchPacket
NpcReleasePacket
NpcMerchantPacket
PlayerTeleportPacket
AnglerQuestPacket
PlayerCompleteAnglerQuestPacket
PlayerAnglerQuestsPacket
TileAnimationPacket
InvasionProgressPacket
ObjectPlacePacket
PlayerChestPacket
ServerCombatNumberPacket
ModulePacket<>
NpcKillsPacket
PlayerStealthPacket
PlayerQuickStackPacket
TileEntityInfoPacket
TileEntityPlacePacket
ItemTweakPacket
ItemFrameInfoPacket
InstancedItemInfoPacket
NpcEmotePacket
NpcStealCoinsPacket
ProjectileRemoveIndexPacket
PlayerPortalPacket
NpcIdKilledPacket
EventOccurredPacket
MinionTargetPositionPacket
NpcPortalPacket
PillarShieldStrengthsPacket
PlayerNebulaPacket
MoonLordCountdownPacket
NpcShopInventoryPacket
GemLockTogglePacket
ServerSmokePacket
ServerChatPacket
ProjectileCannonPacket
WireOperationPacket
PlayerConsumeItemsPacket
BirthdayPartyTogglePacket
TreeGrowEffectPacket
OldOnesArmyStartPacket
OldOnesArmyEndPacket
MinionTargetNpcPacket
OldOnesArmyInfoPacket
PlayerHurtPacket
PlayerKillPacket
ServerCombatTextPacket
PlayerEmojiPacket
MannequinInventoryPacket
TileEntityInteractionPacket
WeaponsRackInfoPacket
HatRackInventoryPacket
BlockBreakingPacket
NpcRevengeInfoPacket
NpcRemoveRevengePacket
GolfBallPacket
ServerConnectedPacket
NpcFishPacket
NpcImmunityPacket
ServerSoundPacket
FoodPlatterInfoPacket
PlayerLuckPacket
PlayerDeadPacket
NpcCavernMonstersPacket
NpcRemoveBuffPacket
PlayerHostPacket
TShock's permission system is made up of a comma-separated list of groups, using SQL only as a storage engine and not what it's supposed to be used for. Most of the performance problems with the AAA system can be resolved by using proper schema.
HasPermission
follows these rules:
IsNegated
immediately returns the value of IsNegated
true
, else it continues on to the next groupfalse
if no groups contain the permissionThere are a few instances of shared namespace and type names:
class Orion
and namespace Orion
class Item
and namespaces Orion.Entities.Item
, Orion.Events.Item
class Npc
and namespaces Orion.Entities.Npc
, Orion.Events.Npc
class Player
and namespaces Orion.Entities.Player
, Orion.Events.Player
class Projectile
and namespaces Orion.Entities.Projectile
, Orion.Events.Projectile
This could lead to a few annoyances later on, and should probably be fixed now. One solution would be to pluralize Orion.Entities.<EntityName>
(and place the events in there too, or pluralize that as well). I'm not sure how we should deal with Orion.Orion
, though.
In one of many discussions with Wolfje, I pointed out that it would be worth developing TShock 5 along side Orion as fast as possible to get momentum going to answer questions like "what actually needs to be in Orion?" The general feedback I got was that development on TShock 5 shouldn't start until a lot of the underlying Orion structure is done, but this poses a problem: how do you develop a framework for a plugin with requirements that don't exist?
To start, I've created a label called "Blocks TShock 5," which should go on issues that completely block starting development on TShock 5. In other words, these are issues that, without being completed, we can't even have a stupid plugin that initializes and says "Hi I'm TShock 5" on Orion without it being completely useless.
Issues that fall under this scope are core, critical bits of functionality like commands, logging and how to properly setup and define an Orion plugin. I'm going to be making issues for things like this and continuing to ask for direction here.
Why? Right now, as a TShock developer who hasn't kept up with Orion development fully, it's still basically impossible for me to contribute useful code to the project because it's unclear what needs to be done and how the design works. I know how it works in an abstract sense but I can't with concrete 100% certainty say where I should start. I'd say this goes for anyone who hasn't submitted pull requests to Orion already. In other words, Orion development is limited to a very small subset of developers, all of which don't necessarily have all the free time in the world to keep development going during slow periods.
Some high level goals also get attached to this issue:
If Orion never gets TShock 5 running on it, it will be a waste. If a we keep having to update the Terraria Server API because of new Terraria releases while Orion development crawls along, Orion is a waste.
Why am I making this issue? Because it's not clear to me, looking at the issue list and reading the current wiki, where I can even dive in and help. I've certainly asked a lot of questions and gotten a basic grasp, but I don't even know where to start. If I don't know where to start, then I doubt many other people outside of the core group here do.
Right now it's pretty clear that the people who can start working on fixing these problems are: @MarioE, @tylerjwatson, @WhiTexz, and @DeathCradle. These fundamental issues need to be resolved because if I start contributing right now, it's going to be the blind leading the blind.
Implement IoC properly, passing around Func<TTerrariaType, OurType>
s in order to wrap the necessary objects in, e.g., ItemService
, PlayerService
, etc.
E.g., Player
constructor needs to be something like this:
public Player(Terraria.Player terrariaPlayer, Func<Terraria.Item, Item> itemWrapper, Func<Terraria.Item[], IItemArray> itemArrayWrapper)
This issue will serve as a tracker for "meta" Orion progress. Items that fall under this category include, but are not limited to, creating developer-oriented documentation so that other developers can begin contributing to Orion, lobbying for support behind Orion, etc.
More items to be added later as needed.
implement a command service which handles commands with parameters that are either invoked by players, the console, or any UI frontend running in Orion.
The basic spec is as follows:
ICommandStreamService
(shared) whose job it is to keep the list of commands, and facilitate the insertion of command handlers at any place in the handler stack, handle the deserialisation of command parameters, and invoke each handler in the listICommandService
whose job it is to yield commands from their method (up to the implementation), and yield the delegates to to the ICommandStreamService
A command service implementation idea is listed below:
AttributeCommandService
: Yields command from attributes placed on instance methods:
[Command("/invsee {player} {-restore:flag}")]
public CommandResult InvseeCommand(IPlayer player, bool? restore = null)
{
}
Commands must not write to Console
or Player
, instead must write to an object stream passed to the command in its parameters. This is to ensure that objects written as output from the commands are always formatted in a consistent manner.
A command may choose to output any object to the output stream, and they are formatted according to the rules of the UI environment which invokes the command. A command may output more than one object to the output stream, which may include one or more exceptions.
For example, a plugin may output an IEnumerable<>
, which will format as a text table in the console, comma-separated list in the game itself, a HTML table in the web UI, or a JSON array as a REST call. An exception written to the object stream may format as red text in the console, or a callout in HTML, etc.
Any .NET type may be written to the object stream, however complex types will be coalesced to string
before output; implement .ToString()
if you want to summarize a human-friendly representation of a custom object.
In the current TShock you cannot ban Wildcard IP's, this is rather annoying as many players have Dynamic IP's.
Example: /ban addw 123.123.123.*
Implement Log4Net to handle logging. This will solve Pryaxis/TShock#917
What do we want our logger to do?
Event handling, in its current form, relies heavily on the C# event
object. This is seriously limited in that services may not define prioritization or handling order.
An internal message queue would allow for all services to write events to the queue and allow all other services to receive and respond to messages in a pub-sub pattern.
Since I've pushed a ton of stuff to the reboot
branch, I want to recap what's been done there.
Orion's goal is to act as an intermediary layer between OTAPI/Terraria and TShock/other plugins. The way in which this is done is by providing interfaces such as INpc
and INpcService
to consumers such as TShock. There are three main reasons for this:
INpc
by creating its own INpcService
and allow NPCs to have extra features.To create a service, you should create an interface that inherits from IService
and an implementation that inherits from OrionService
and implements your interface. The Author
, Name
, and Version
properties can be overridden as necessary.
public interface ICommandService : IService {
void ExecuteCommand(string input);
}
internal class TShockCommandService : OrionService, ICommandService {
public void ExecuteCommand(string input) { }
}
To create a plugin, your implementation should inherit from OrionPlugin
. It isn't necessary to create a plugin interface, but you could do that if you wanted to. Your constructor should include all of the plugins and services you'll need, as they'll be injected for you.
public class WorldEditPlugin : OrionPlugin {
public override string Author => "Kevin";
public override string Name => "WorldEdit";
private readonly IWorldService _worldService;
public WorldEditPlugin(OrionKernel kernel, IWorldService worldService) : base(kernel) {
_worldService = worldService ?? throw new ArgumentNullException(nameof(worldService));
}
}
To register a hook, just use +=
on the relevant HookHandlerCollection<>
with a valid handler. This is thread-safe since HookHandlerCollection<>
is immutable.
_worldService.SavingWorld += SavingWorldHandler
...
[HookHandler(HookPriority.Lowest)]
private void SavingWorldHandler(object sender, SavingWorldEventArgs args) {
}
Logging is handled via Serilog. To log something, just access the static Log
class:
Log.Verbose("Verbose message");
Log.Debug("Debug message");
Log.Information("Information message");
Log.Warning("Warning message");
Log.Error("Error message");
Log.Fatal("Fatal message");
By default, the Orion launcher sinks this log to the console and a daily rolling file under the logs/
directory.
Listed below is the functionality that Orion currently has. I don't think the services that we offer are going to change any time soon.
IItemService
: access IItem
instances, item-related hooks, and item-related methods (SpawnItem
, etc.).INetworkService
: access IClient
instances, network-related hooks, and network-related methods (BroadcastPacket
, etc.). There is an entire type-safe packet library implemented under Orion.Networking.Packets
to facilitate usage.INpcService
: access INpc
instances, NPC-related hooks, and NPC-related methods (SpawnNpc
, etc.).IPlayerService
: access IPlayer
instances and player-related hooks.IProjectileService
: access IProjectile
instances, projectile-related hooks, and projectile-related methods (SpawnProjectile
, etc.).IChestService
: access IChest
instances and chest-related methods (AddChest
, GetChest
, etc.).ISignService
: access ISign
instances and sign-related methods (AddSign
, GetSign
, etc.).IWorldService
access Tile
instances, world-related hooks, and world-related methods (Save
, etc.).In addition, all objects above are annotatable, allowing users to get or set annotations for each instance. This makes it easy for these objects to be extended.
Orion will not be responsible for providing commands, configurations, or users, groups, or permissions. This is to be handled by plugins such as TShock, and allows Orion to remain as light-weight as possible.
If you have any comments or suggestions, please respond with them.
We need a hook matrix of which hooks are featured inside TSAPI and TShock, compared with ones implemented in OTAPI
These hooks exist inside TShock and TSAPI as at APIVersion 1.21
Source | Name | PropertyType | In OTAPI |
---|---|---|---|
TSAPI | GameUpdate | typeof(HandlerCollection) | [x] |
TSAPI | GamePostUpdate | typeof(HandlerCollection) | [x] |
TSAPI | GameHardmodeTileUpdate | typeof(HandlerCollection) | [ ] |
TSAPI | GameInitialize | typeof(HandlerCollection) | [x] |
TSAPI | GamePostInitialize | typeof(HandlerCollection) | [x] |
TSAPI | GameWorldConnect | typeof(HandlerCollection) | [ ] |
TSAPI | GameWorldDisconnect | typeof(HandlerCollection) | [ ] |
TSAPI | GameStatueSpawn | typeof(HandlerCollection) | [x] |
TSAPI | ItemSetDefaultsInt | "typeof(HandlerCollection<SetDefaultsEventArgs<Item,Int32>>)" | [x] |
TSAPI | ItemSetDefaultsString | "typeof(HandlerCollection<SetDefaultsEventArgs<Item,String>>)" | [x] |
TSAPI | ItemNetDefaults | "typeof(HandlerCollection<SetDefaultsEventArgs<Item,Int32>>)" | [x] |
TSAPI | NetSendData | typeof(HandlerCollection) | [x] |
TSAPI | NetGetData | typeof(HandlerCollection) | [x] |
TSAPI | NetGreetPlayer | typeof(HandlerCollection) | [x] |
TSAPI | NetSendBytes | typeof(HandlerCollection) | [x] |
TSAPI | NetNameCollision | typeof(HandlerCollection) | [x] |
TSAPI | NpcSetDefaultsInt | "typeof(HandlerCollection<SetDefaultsEventArgs<NPC,Int32>>)" | [x] |
TSAPI | NpcSetDefaultsString | "typeof(HandlerCollection<SetDefaultsEventArgs<NPC,String>>)" | [x] |
TSAPI | NpcNetDefaults | "typeof(HandlerCollection<SetDefaultsEventArgs<NPC,Int32>>)" | [x] |
TSAPI | NpcStrike | typeof(HandlerCollection) | [x] |
TSAPI | NpcTransform | typeof(HandlerCollection) | [x] |
TSAPI | NpcSpawn | typeof(HandlerCollection) | [x] |
TSAPI | NpcLootDrop | typeof(HandlerCollection) | [x] |
TSAPI | NpcTriggerPressurePlate | typeof(HandlerCollection<TriggerPressurePlateEventArgs>) | [x] |
TSAPI | DropBossBag | typeof(HandlerCollection) | [x] |
TSAPI | PlayerUpdatePhysics | typeof(HandlerCollection) | [ ] |
TSAPI | PlayerTriggerPressurePlate | typeof(HandlerCollection<TriggerPressurePlateEventArgs>) | [x] |
TSAPI | ProjectileSetDefaults | "typeof(HandlerCollection<SetDefaultsEventArgs<Projectile,Int32>>)" | [x] |
TSAPI | ProjectileTriggerPressurePlate | typeof(HandlerCollection<TriggerPressurePlateEventArgs>) | [x] |
TSAPI | ProjectileAIUpdate | typeof(HandlerCollection) | [x] |
TSAPI | ServerCommand | typeof(HandlerCollection) | [ ] |
TSAPI | ServerConnect | typeof(HandlerCollection) | [ ] |
TSAPI | ServerJoin | typeof(HandlerCollection) | [ ] |
TSAPI | ServerLeave | typeof(HandlerCollection) | [ ] |
TSAPI | ServerChat | typeof(HandlerCollection) | [ ] |
TSAPI | ServerBroadcast | typeof(HandlerCollection) | [ ] |
TSAPI | ServerSocketReset | typeof(HandlerCollection) | [ ] |
TSAPI | WorldSave | typeof(HandlerCollection) | [x] |
TSAPI | WorldStartHardMode | typeof(HandlerCollection) | [x] |
TSAPI | WorldMeteorDrop | typeof(HandlerCollection) | [ ] |
TSAPI | WorldChristmasCheck | typeof(HandlerCollection) | [x] |
TSAPI | WorldHalloweenCheck | typeof(HandlerCollection) | [x] |
TShock | AccountCreate | typeof(AccountCreateD) | n/a |
TShock | AccountDelete | typeof(AccountDeleteD) | n/a |
TShock | ReloadEvent | typeof(ReloadEventD) | n/a |
TShock | PlayerPostLogin | typeof(PlayerPostLoginD) | n/a |
TShock | PlayerPreLogin | typeof(PlayerPreLoginD) | n/a |
TShock | PlayerLogout | typeof(PlayerLogoutD) | n/a |
TShock | PlayerCommand | typeof(PlayerCommandD) | [ ] |
TShock | PlayerChat | typeof(PlayerChatD) | [ ] |
TShock | RegionEntered | typeof(RegionEnteredD) | n/a |
TShock | RegionLeft | typeof(RegionLeftD) | n/a |
TShock | RegionCreated | typeof(RegionCreatedD) | n/a |
TShock | RegionDeleted | typeof(RegionDeletedD) | n/a |
I don't know if 'sandboxing' is the right term, but one thing that's been an issue in TShock is that once plugins are loaded they're stuck. You can't unload nor reload a plugin. Since the method we use to load plugins has been to load the plugin types out of third party assemblies into the TShock AppDomain
we can't really unload or reload them.
This issue highlights development made towards the progress of loading plugins in a way which allows their types to be disposed of and reloaded. The way to do this would be to load the types into a separate AppDomain
. This can be done manually or facilitated with the System.AddIn
namespace.
From Slack, #orion-info, there was a discussion with some discrepancy on whether or not plugin hot reloading / reloading would be a part of Orion. According to @ProfessorXz, @MarioE has stated this isn't going to be a feature, but @Enerdy said that the previous reason why we didn't have hotloading in the API was because of issues loading and reloading assemblies. In addition, @Enerdy pointed out that with injection, we should be able to do something similar to this.
So, @MarioE, and everyone in general (cc @NyxStudios/tshock), what's the status on plugin reloading / hot reloading in Orion as far as development targets go?
The general consensus in server applications is that each person connecting is given a User
object. This object serves as the main interaction point between the server and the client. Users may register to the system which creates a user Account
associated with the user object. Typically, accounts are simply database information that grants the user access to certain features when authenticated to. This issue addresses the nomenclature changes made in a recent commit by defining why we should name our classes in a certain way.
Because Terraria is a game, clients are associated with a Player
object which controls how the client interacts with the server. We may call this player object our User
. An Orion player is able to authenticate to a user account to be granted access to that account's settings and permissions. In TShock 4, however, we used a User
class as a database structure that simply hold information to be stored/retrieved. Users weren't even linked to TSPlayer objects until recently; we'd just store the user id and name on the player object.
Because naming database accounts "User" can be deceiving, and "UserAccount" is an unnecessarily long name for the context, I suggest the following changes to normalize naming in services:
IAccountService
. This makes it fit better alongside IGroupService
and IPermissionService
as we're not prefixing those with User
.Account
, as we can assume they refer to the accounts used by our players. This partially reverts the latest changes by me taking the opposite approach now that conventions have been made clear to me.A pull request will be made with proper GitHub Flow guidelines applying these changes. Should this be the nomenclature of choice for our AAA services?
This issue was based on a Slack discussion. Most points have been covered here through a diverse approach.
As title
Define and implement a Tile
abstraction.
Rewrite the database system using Dapper.
Should preferably conform to relational database standards
TShock's REST API v2 is out for quite some time now and it would be nice to have a next version. This gives room to fix some inconsistencies, remove duplication, fix issues and add features.
For example purposes, I will use 127.0.0.1
for the host and 7878
for the REST port. Also, because JSON does not officially support comments, I "faked" the comments.
One issue we currently face is that you have to use v1 for some endpoints and for some other endpoints v2. In v3 everything should be called from http://127.0.0.1:7878/v3/.
Keys MUST be lower_under
.
A value SHOULD NOT contain multiple values. buffs
from REST v2 is a good example of how it should not be done.
{
"//": "...",
"buffs": "147, 86, 158, 146, 87, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0"
}
All dates SHOULD be available as specified in ISO 8601.
Complete date plus hours and minutes:
YYYY-MM-DDThh:mmTZD (eg 1997-07-16T19:20+01:00)
GET can expose sensitive data. And besides,
the convention has been established that the GET and HEAD methods SHOULD NOT have the significance of taking an action other than retrieval. These methods ought to be considered "safe".
— http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1
Like in v1 and v2, the token should not be in the URL.
Usernames, passwords, session tokens, and API keys should not appear in the URL, as this can be captured in web server logs, which makes them intrinsically valuable.
Deprecates: /token/create
http://127.0.0.1:7878/v3/token/generate
data:
username = 'restuser'
password = 'restpass'
Example response:
{
"status": 201,
"response": "Successfully generated token.",
"data": {
"token": "5C58D5332D5A51DCC5CFFEBD3813014ECF0248BA104CF12ADE0F93E192E77347",
"expiration_date": "2015-7-24T13:39Z"
}
}
Deprecates: /tokentest
http://127.0.0.1:7878/v3/token/validate
data:
token = '5C58D5332D5A51DCC5CFFEBD3813014ECF0248BA104CF12ADE0F93E192E77347'
Example response:
{
"status": 200,
"response": "Token is valid.",
"data": {
"token": "5C58D5332D5A51DCC5CFFEBD3813014ECF0248BA104CF12ADE0F93E192E77347",
"expiration_date": "2015-7-24T13:39Z"
}
}
Deprecates: /v2/status
, /v2/server/status
http://127.0.0.1:7878/v3/server/info
data:
token = '5C58D5332D5A51DCC5CFFEBD3813014ECF0248BA104CF12ADE0F93E192E77347'
Example response:
{
"status": 200,
"data": {
"name": "TerraServer",
"host": "127.0.0.1",
"port": "7777",
"software": {
"name": "tshock",
"version": "4.4.0"
}
}
}
Deprecates: /v2/server/rawcmd
, /v2/world/butcher
, /world/meteor
, /world/bloodmoon/{bool}
, /v2/players/kick
, /v2/players/kill
, /v2/players/mute
, /v2/players/unmute
, /v2/world/save
, /v2/server/broadcast
http://127.0.0.1:7878/v3/server/execute-command
data:
token = '5C58D5332D5A51DCC5CFFEBD3813014ECF0248BA104CF12ADE0F93E192E77347'
command = 'time noon'
Example response:
{
"status": 200,
"response": "Server set the time to 12:00."
}
http://127.0.0.1:7878/v3/server/update-password
data:
token = '5C58D5332D5A51DCC5CFFEBD3813014ECF0248BA104CF12ADE0F93E192E77347'
password = '123123'
Example response:
{
"status": 200,
"response": "Successfully changed server password."
}
Deprecates: /v2/server/off
http://127.0.0.1:7878/v3/server/shut-down
data:
token = '5C58D5332D5A51DCC5CFFEBD3813014ECF0248BA104CF12ADE0F93E192E77347'
save = false // Optional; default true
Example response:
{
"status": 200,
"response": "Server shut down. World not saved."
}
New feature
http://127.0.0.1:7878/v3/server/start-up
data:
token = '5C58D5332D5A51DCC5CFFEBD3813014ECF0248BA104CF12ADE0F93E192E77347'
port = 8888 // Optional; default 7777
Example response:
{
"status": 200
}
Deprecates: /world/read
, /v2/server/status
http://127.0.0.1:7878/v3/world
data:
token = '5C58D5332D5A51DCC5CFFEBD3813014ECF0248BA104CF12ADE0F93E192E77347'
Example response:
{
"status": 200,
"data": {
"id": 18624254254,
"name": "World 3",
"size": {
"type": "medium",
"width": 6400,
"height": 1800
},
"mode": "expert",
"creation_date": "2015-7-24T13:39Z",
"//": "In what format is the time??",
"time": "42149",
"is_day": true,
"is_bloodmoon": false,
"//": "TODO: Improve invasionsize.",
"invasionsize": 0
}
}
Deprecates: /v2/world/autosave/state/{bool}
http://127.0.0.1:7878/v3/world/auto-save
data:
token = '5C58D5332D5A51DCC5CFFEBD3813014ECF0248BA104CF12ADE0F93E192E77347'
mode = false // Optional; default true
Example response:
{
"status": 200,
"response": "Successfully set auto-save to true."
}
Deprecates: /v2/groups/list
, /v2/groups/read
http://127.0.0.1:7878/v3/groups
data:
token = '5C58D5332D5A51DCC5CFFEBD3813014ECF0248BA104CF12ADE0F93E192E77347'
Example response:
{
"status": 200,
"response": "Successfully fetched 8 groups.",
"data": {
"groups": [
{
"name": "default",
"parent": "guest",
"rgb_chat_color": [
255,
255,
255
],
"permissions": [
"tshock.reservedslot"
],
"negatedpermissions": [],
"totalpermissions": [
"tshock.reservedslot",
"tshock.warp",
"// ...",
"tshock.canchat"
]
},
"// ..."
]
}
}
Deprecates: /v2/groups/create
http://127.0.0.1:7878/v3/group/create
data:
token = '5C58D5332D5A51DCC5CFFEBD3813014ECF0248BA104CF12ADE0F93E192E77347'
name = 'Team Penguin'
prefix = '(Team Penguin)'
suffix = null
parent_type = 'id' // Optional; defaults to "name"; options: id, name
parent_group = 23
permissions = [
'tshock.tp.self',
'tshock.tp.others'
]
rgb_chat_color = [
255,
255,
255
]
Example response:
{
"status": 201,
"response": "Successfully created group \"Team Penguin\"."
}
Deprecates: /v2/groups/update
http://127.0.0.1:7878/v3/group/update
data:
token = '5C58D5332D5A51DCC5CFFEBD3813014ECF0248BA104CF12ADE0F93E192E77347'
type = 'id' // Optional; defaults to "name"; options: id, name
name = 24
prefix = '(Team Penguin)'
suffix = null
parent_type = 'id' // Optional; defaults to "name"; options: id, name
parent_group = 23
permissions = [
'tshock.tp.self',
'tshock.tp.others'
]
rgb_chat_color = [
255,
255,
255
]
Example response:
{
"status": 201,
"response": "Successfully created group \"Team Penguin\"."
}
Deprecates: /v2/groups/destroy
http://127.0.0.1:7878/v3/group/delete
data:
token = '5C58D5332D5A51DCC5CFFEBD3813014ECF0248BA104CF12ADE0F93E192E77347'
type = 'id' // Optional; defaults to "name"; options: id, name
group = 24
Example response:
{
"status": 200,
"response": "Successfully deleted group \"Team Penguin\"."
}
Relevant: Pryaxis/TShock#901
Deprecates: /v2/users/read
http://127.0.0.1:7878/v3/users
data:
token = '5C58D5332D5A51DCC5CFFEBD3813014ECF0248BA104CF12ADE0F93E192E77347'
Example response:
{
"status": 200,
"response": "Successfully fetched 4 online users.",
"data": {
"maximum_allowed": 7,
"online": [
{
"nickname": "Ped",
"username": "ped",
"group": "superadmin",
"team": 0,
"ip": "127.0.0.1",
"position": {
"longtitude": 3219,
"latitude": 290
},
"buffs": [
{
"id": 147,
"name": "Banners",
"description": [
"Increased damage and defense from the following:",
"Zombie",
"Green Slime"
],
"seconds_left": null
},
{
"id": 121,
"name": "Fishing",
"description": "Increased fishing level",
"seconds_left": 480
}
],
"inventory": {
"items": [
{
"id": 200,
"name": "Green Phaseblade",
"description": [
"23 melee damage",
"9% critical strike chance",
"Fast speed",
"Weak knockback",
"Material",
"+5% damage",
"+15% knockback"
],
"prefix": {
"id": 7,
"name": "Unpleasant"
},
"amount": 1
},
"// x50 elements"
],
"coins": [
"// Same as items, but without prefix",
"// x4 elements"
],
"ammo": [
"// Same as items, but without prefix",
"// x4 elements"
],
"trash": null,
"holding_item": null,
"helmet": {
"dye": {
"id": 213,
"name": "Brown Dye",
"description": [
"Material"
]
},
"vanity": {
"//": "Same as dye, but with prefix",
"prefix": {
"id": 7,
"name": "Unpleasant"
}
},
"item": {
"//": "Same as dye, but with prefix",
}
},
"shirt": {
"//": "Same as helmet"
},
"pants": {
"//": "Same as helmet"
},
"accessories": [
{
"dye": {
"//": "Same as helmet's dye"
},
"vanity": {
"//": "Same as helmet's vanity"
},
"item": {
"//": "Same as dye, but with prefix"
}
}
"// x5 elements"
],
"pet": {
"dye": {
"//": "Same as helmet's dye"
},
"item": {
"//": "Same as helmet's item"
}
},
"light_pet": {
"//": "Same as pet"
},
"mount": {
"//": "Same as pet"
},
"minecart": {
"//": "Same as pet"
},
"grappling_hook": {
"//": "Same as pet"
}
}
},
"// Other players"
]
}
}
Relevant: Pryaxis/TShock#901
Deprecates: /v2/server/status
, /v2/users/activelist
, /v2/players/read
, /v2/players/list
http://127.0.0.1:7878/v3/online-users
data:
token = '5C58D5332D5A51DCC5CFFEBD3813014ECF0248BA104CF12ADE0F93E192E77347'
Example response:
{
"status": 200,
"response": "Successfully fetched 4 online users.",
"data": {
"maximum_allowed": 7,
"online": [
{
"nickname": "Ped",
"username": "ped",
"group": "superadmin",
"team": 0,
"ip": "127.0.0.1",
"position": {
"longtitude": 3219,
"latitude": 290
},
"buffs": [
{
"id": 147,
"name": "Banners",
"description": [
"Increased damage and defense from the following:",
"Zombie",
"Green Slime"
],
"seconds_left": null
},
{
"id": 121,
"name": "Fishing",
"description": "Increased fishing level",
"seconds_left": 480
}
],
"inventory": {
"items": [
{
"id": 200,
"name": "Green Phaseblade",
"description": [
"23 melee damage",
"9% critical strike chance",
"Fast speed",
"Weak knockback",
"Material",
"+5% damage",
"+15% knockback"
],
"prefix": {
"id": 7,
"name": "Unpleasant"
},
"amount": 1
},
"// x50 elements"
],
"coins": [
"// Same as items, but without prefix",
"// x4 elements"
],
"ammo": [
"// Same as items, but without prefix",
"// x4 elements"
],
"trash": null,
"holding_item": null,
"helmet": {
"dye": {
"id": 213,
"name": "Brown Dye",
"description": [
"Material"
]
},
"vanity": {
"//": "Same as dye, but with prefix",
"prefix": {
"id": 7,
"name": "Unpleasant"
}
},
"item": {
"//": "Same as dye, but with prefix",
}
},
"shirt": {
"//": "Same as helmet"
},
"pants": {
"//": "Same as helmet"
},
"accessories": [
{
"dye": {
"//": "Same as helmet's dye"
},
"vanity": {
"//": "Same as helmet's vanity"
},
"item": {
"//": "Same as dye, but with prefix"
}
}
"// x5 elements"
],
"pet": {
"dye": {
"//": "Same as helmet's dye"
},
"item": {
"//": "Same as helmet's item"
}
},
"light_pet": {
"//": "Same as pet"
},
"mount": {
"//": "Same as pet"
},
"minecart": {
"//": "Same as pet"
},
"grappling_hook": {
"//": "Same as pet"
}
}
},
"// Other players"
]
}
}
Deprecates: /v2/users/create
http://127.0.0.1:7878/v3/user/create
data:
token = '5C58D5332D5A51DCC5CFFEBD3813014ECF0248BA104CF12ADE0F93E192E77347'
username = 'ped'
password = '123123'
group = 'moderator' // Optional; defaults to "default"
Example response:
{
"status": 201,
"response": "Successfully created moderator \"ped\"."
}
Deprecates: /v2/users/update
http://127.0.0.1:7878/v3/user/update
data:
token = '5C58D5332D5A51DCC5CFFEBD3813014ECF0248BA104CF12ADE0F93E192E77347'
type = 'id' // Optional; defaults to "username"; options: id, username
user = 254
// TODO: Add more parameters
Example response:
{
"status": 200,
"response": "Successfully updated user 254."
}
Deprecates: /v2/users/destroy
http://127.0.0.1:7878/v3/user/delete
data:
token = '5C58D5332D5A51DCC5CFFEBD3813014ECF0248BA104CF12ADE0F93E192E77347'
type = 'id' // Optional; defaults to "username"; options: id, username
user = 254
Example response:
{
"status": 200,
"response": "Successfully deleted user from database."
}
Deprecates: /v2/players/ban
v1 is out for a very long time. I propose to remove the support to get rid of legacy code. I also propose to deprecate v2 and remove it in August 2018.
Feel free to comment on this. I might have missed something or made some mistakes along the way.
@WhiTexz If v3 is already out, then you can obviously name it v4. I am not aware of v3.
The below contains a list of general items to be completed in the Orion Framework
This should ideally be worked around / hacked around somewhere in here or OTAPI to prevent any regression or related issues from moving to Orion instead of TSAPI. See Pryaxis/TShock#1023 for more context.
Set up an automated documentation workflow.
Define and implement an abstraction for the Terraria world. Implement world-related events, as well.
Currently, there are EventHandlerCollection<TEventArgs>
properties on all of the services. To register an event, you'd do something like the following:
private readonly Lazy<PlayerService> _playerService;
// ...
_playerService.Value.PlayerJoin.RegisterHandler(PlayerJoinHandler, Log);
// ...
[EventHandler(EventPriority.Monitor)]
public void PlayerJoinHandler(object? sender, PlayerJoinEventArgs e) {
Log.Debug("{Player} joined", e.Player);
}
However, another possible way to register events could be like the following:
Kernel.RegisterEvents(this);
// ...
[EventHandler(EventPriority.Monitor)]
public void OnPlayerJoin(PlayerJoinEventArgs e) {
Log.Debug("{Player} joined", e.Player);
}
[EventHandler(EventPriority.Monitor)]
public void OnPlayerQuit(PlayerQuitEventArgs e) {
Log.Debug("{Player} quit", e.Player);
}
The idea is that OrionKernel.RegisterEvents
will take all methods from the given object, filtering using the ones with EventHandlerAttribute
annotating them, and register them automatically. There would be no requirement to define any EventHandlerCollection
s anywhere, they'd just get aggregated in the kernel. Additionally, to invoke the events, you would just do the following:
Kernel.InvokeEvent(new PlayerJoinEventArgs(player));
Kernel.InvokeEvent(new PlayerQuitEventArgs(player));
TL:DR; first approach is explicit, second approach is more similar to Bukkit's. What are your thoughts on these two approaches?
How would we like bans to work?
I believe there was a brief discussion in Slack involving a suggestion to simply have an 'enabled/disabled' flag on each account.
Plugins simply modify this value to 'ban' a user and track any other information they may need independently (e.g., banned/disabled datetime, reason, who banned the account).
Suggestions? Feedback?
This issue is to track a presentation to other stakeholders what orion actually is, and to go through an end-to-end use case for why some of Orion's design decisions are the way they are, and to discuss the merits between them
Outcomes of the presentation:
Once the above has been achieved, it's then possible to document it out.
Materials:
Notes:
Terraria
staticsOrion Architecture:
More to come as I think of them
I think we should standardize error messages and feedback, like what changed in Pryaxis/TShock@c4cf2d4 with /register
in TShock.
I think any error message that gets sent to either the console or a player should have not only a clear message, but a reasonable solution attached too.
Bad:
Good:
In other words, error messages should be both informative and actionable. If the error message is something we can't expect someone to solve, we should make it clear that they need to report the issue on the forums or on Github. Look at what Ruby does if Ruby dies:
[NOTE]
You may have encountered a bug in the Ruby interpreter or extension libraries.
Bug reports are welcome.
Don't forget to include the above Crash Report log file.
For details: http://www.ruby-lang.org/bugreport.html
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.