Skip to content
This repository has been archived by the owner on Nov 4, 2023. It is now read-only.

Commit

Permalink
fix: bummery map types
Browse files Browse the repository at this point in the history
  • Loading branch information
cdaringe committed Jan 25, 2021
1 parent eed3dda commit 6807c16
Show file tree
Hide file tree
Showing 21 changed files with 1,448 additions and 596 deletions.
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"ts-node/register/transpile-only",
"--max-old-space-size=8000"
],
"args": ["${workspaceRoot}/src/bin.ts"],
"args": ["${workspaceRoot}/src/bin.ts", "-l", "ts"],
"cwd": "${workspaceRoot}",
"skipFiles": [],
"console": "integratedTerminal"
Expand Down
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"deno.enable": true
"deno.enable": false
}
36 changes: 2 additions & 34 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# A statically generated blog example using Next.js and Markdown
# A statically site using Next.js and Markdown

This example showcases Next.js's [Static Generation](https://nextjs.org/docs/basic-features/pages) feature using Markdown files as the data source.

The blog posts are stored in `/_posts` as Markdown files with front matter support. Adding a new Markdown file in there will create a new blog post.
The posts are stored in `/_posts` as Markdown files with front matter support. Adding a new Markdown file in there will create a new blog post.

To create the blog posts we use [`remark`](https://github.com/remarkjs/remark) and [`remark-html`](https://github.com/remarkjs/remark-html) to convert the Markdown files into an HTML string, and then send it down as a prop to the page. The metadata of every post is handled by [`gray-matter`](https://github.com/jonschlinkert/gray-matter) and also sent in props to the page.

Expand All @@ -14,38 +14,6 @@ To create the blog posts we use [`remark`](https://github.com/remarkjs/remark) a

Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example):

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/blog-starter&project-name=blog-starter&repository-name=blog-starter)

### Related examples

- [WordPress](/examples/cms-wordpress)
- [DatoCMS](/examples/cms-datocms)
- [Sanity](/examples/cms-sanity)
- [TakeShape](/examples/cms-takeshape)
- [Prismic](/examples/cms-prismic)
- [Contentful](/examples/cms-contentful)
- [Strapi](/examples/cms-strapi)
- [Agility CMS](/examples/cms-agilitycms)
- [Cosmic](/examples/cms-cosmic)
- [ButterCMS](/examples/cms-buttercms)
- [Storyblok](/examples/cms-storyblok)
- [GraphCMS](/examples/cms-graphcms)
- [Kontent](/examples/cms-kontent)

## How to use

Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example:

```bash
npx create-next-app --example blog-starter blog-starter-app
# or
yarn create next-app --example blog-starter blog-starter-app
```

Your blog should be up and running on [http://localhost:3000](http://localhost:3000)! If it doesn't work, post on [GitHub discussions](https://github.com/vercel/next.js/discussions).

Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).

# Notes

This blog-starter uses [Tailwind CSS](https://tailwindcss.com). To control the generated stylesheet's filesize, this example uses Tailwind CSS' v2.0 [`purge` option](https://tailwindcss.com/docs/controlling-file-size/#removing-unused-css) to remove unused CSS.
35 changes: 35 additions & 0 deletions docs/_posts/faq.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
title: faq
excerpt: Frequently Asked Questions
# coverImage: "/factorio-type-kit/assets/blog/hello-world/cover.jpg"
date: "2021-01-20T05:35:07.322Z"
author:
name: cdaringe
picture: "/factorio-type-kit/assets/blog/authors/cdaringe.jpeg"
# ogImage:
# url: "/factorio-type-kit/assets/blog/hello-world/cover.jpg"
---

<!-- # FAQ -->

## Do I have to use TypeScript?

If you want to convert the Lua API into _something else_, you have (undeveloped) options.

Option 1:

- use the factorio-type-kit CLI to emit JSON (IR)
- convert that JSON to anything you desire

Option 2:

- [Add a printer here](https://github.com/cdaringe/factorio-type-kit/tree/main/src/printers).


## The generated code adds `:` characters to access methods and properties, versus `.` characters

Read about JS vs Lua [this binding](https://typescripttolua.github.io/docs/the-self-parameter#removing-it), tuneable in your code & the compiler.

## Can I use npm libraries?

There is no recipe for this yet. Perhaps some webpack-like bundling could enable this. If you get a working recipe, send a patch!
219 changes: 200 additions & 19 deletions docs/_posts/get-started.md
Original file line number Diff line number Diff line change
@@ -1,47 +1,228 @@
---
title: Get started modding in TypeScript
title: Get started Factorio modding in TypeScript
excerpt: Factorio mods use Lua, but Lua is dynamically typed. Fear not--static typing is here for factorio.
coverImage: "/factorio-type-kit/assets/blog/dynamic-routing/cover.jpg"
# coverImage: "/factorio-type-kit/assets/blog/dynamic-routing/cover.jpg"
date: "2021-01-24T05:35:07.322Z"
author:
name: cdaringe
picture: "/factorio-type-kit/assets/blog/authors/cdaringe.jpeg"
ogImage:
url: "/factorio-type-kit/assets/blog/dynamic-routing/cover.jpg"
# ogImage:
# url: "/factorio-type-kit/assets/blog/dynamic-routing/cover.jpg"
---

Type safety is in. In fact, it always has been! While typescript doesn't offer
the strongest safety guarantees, it _does_ offer moderate safety, and works well

Programming with type safety is _in™_. In fact, it always has been! While TypeScript doesn't offer
the strongest safety guarantees of any language under the sun, it _does_ offer moderate safety, and works well
as a Lua replacement. The following steps are required to author factorio mods in TS:

1. Install [node](https://nodejs.org/). I recommend using [fnm](https://github.com/Schniz/fnm#using-a-script-macoslinux) for installing nodejs installations.
## Factorio in TypeScript - Outline

We will execute the following steps later, but at a glance, here are the general steps in TS Factorio mod development:

1. Install [node](https://nodejs.org/). I recommend using [fnm](https://github.com/Schniz/fnm#using-a-script-macoslinux) for installing and managing nodejs installations.
2. (optional) Install [yarn](https://classic.yarnpkg.com/en/docs/install). `npm` is the default package manager for nodejs, but the following document uses `yarn`. It takes only moments to install.
3. Setup a `nodejs` project with typescript installed
4. Install & configure [TypeScriptToLua](https://github.com/TypeScriptToLua/TypeScriptToLua)
5. Install & configure `factorio-type-kit`
3. Setup a `nodejs` project
4. Install `factorio-type-kit`, [typescript](https://www.typescriptlang.org/#installation), and [TypeScriptToLua](https://github.com/TypeScriptToLua/TypeScriptToLua)
5. Configure the typescript compiler to use TypeScriptToLua
6. Code
7. Deploy
7. Package & deploy the mod

It is assumed that the reader has installed node & yarn before proceeding further.

Let's get to work.

Assuming the reader has installed node & yarn, to expedite a handful of intermediate steps, the following command can be run to bootstrap a fresh mod project _automatically_:
## Bootstrapping a barebones mod

```sh
The following command can be run to bootstrap a fresh mod project _automatically_:

<!-- <Shell children='$ yarn create factorio-mod <mod-name>' /> -->
```shell
$ yarn create factorio-mod <mod-name>
```

I will create a mod called `vroom`. This mod will make the character accelerate as he runs, allowing the player to travel at warp speeds!
For demonstrating purposes, I will create a mod called `vroom`. This mod will make the character accelerate as he runs, allowing the player to travel at warp speeds!

```sh
```shell
$ yarn create factorio-mod vroom

...

Victory! Your mod is ready to roll. Execute the following commands to get going:
- cd "/Users/cdaringe/src/vroom"
- yarn start
✨ Done in 5.75s.
```

Nice! After I `cd "/Users/cdaringe/src/vroom"`, I'll open my editor (`code .`),
and study the layout.
This step automatically installs the aforementioned dependencies and sets up the compiler toolchain.
If you want to do these steps manually, please see the [vroom]() codebase, and replicate the artifacts hosted there.

## Understanding the barebones mod

Let us `cd "path/to/vroom"`, and study the content. If you
are familiar with the [factorio mod directory stucture](https://wiki.factorio.com/Tutorial:Mod_structure),
this should look quite familiar.

```shell
~/src/vroom via ⬢ v15.0.1
❯ ls -alh
Permissions Size User Date Modified Name
.rw-r--r-- 167 cdaringe 24 Jan 11:37 control.ts
.rw-r--r-- 183 cdaringe 24 Jan 11:37 info.json
drwxr-xr-x - cdaringe 24 Jan 11:37 node_modules
.rw-r--r-- 247 cdaringe 24 Jan 11:37 package.json
.rw-r--r-- 131 cdaringe 24 Jan 11:37 readme.md
.rw-r--r-- 331 cdaringe 24 Jan 11:37 tsconfig.json
.rw-r--r-- 35k cdaringe 24 Jan 11:37 yarn.lock
```
<!-- `.trim()} /> -->

- `info.json` - the entrypont to all factorio mods, pre-populated with some essentials
- `control.ts` - this file will compile to `control.lua`, which Factorio uses to run your mod!
- `tsconfig.json` - typescript configuration, critically including [TypescriptToLua configuration](https://typescripttolua.github.io/docs/configuration)
- `package.json` & `node_modules` - resources to support your TS development experience :)

Let's open up `control.ts`:

```typescript
const onTick = (_evt: OnTickPayload) => {
game.print(serpent.block({
hello: "world",
its_nice: "to see you"
}));
};

script.on_event(defines.events.on_tick, onTick);
```

- `script.on_event` - typed
- `game` - typed
- `serpent` - typed
- `evt` - typed, via casting

Boom! Types for everything!

...but not quite. The Factorio Lua documentation is _lossy_, and has many cases where we simply _cannot_ parse correct type definitions from their HTML. Until the factorio development team publicly supports machine-readable API documentation, `factorio-type-kit` TypeScript provisions will generally only be 80-90%+ complete. Thus, TypeScript users will be periodically disappointed to find `any` or `unknown` types in various interfaces. With that said, `factorio-type-kit` users are welcome to manually backfill essential definitions to make our community's development kit better.

## Starting the mod compiler

```shell
$ yarn start
```

Momentarily after launching the watching compiler, `control.lua` should be emitted. Open it up:


```lua
onTick = function(_evt)
game.print(
serpent.block({hello = "world", its_nice = "to see you"})
)
end
script.on_event(defines.events.on_tick, onTick)
```

The TypeScriptToLua compiler does a fanstastic job on producing readable Lua.

## Implementing vroom

The very first thing the `vroom` mod needs to do is detect when the player is in motion.

Rather that pull up the documentation, I'll just open up another call `script.on_event`,
begin typing in `defines.events.`, let intellisense present me options, and discover `on_player_changed_position`.

```typescript
script.on_event(
defines.events.on_player_changed_position,
onPositionChange
);
```

`onPositionChange` is not defined yet. Let's discuss about what it needs to do then draft it.
First, it needs to get the player's current speed. Next, the speed needs to increase it when running. Because the purpose of the mod is to accelerate the user, we must multiply the previous speed by a scalar every so often.

```typescript
const onPositionChange = (evt: OnPlayerChangedPositionPayload) => {
const player = game.get_player(evt.player_index);
if (player.walking_state.walking) {
// ...
}
};
```

Immediately take note that we have manually specified the type of `evt` as `OnPlayerChangedPositionPayload`. All factorio script events follow the event naming convention: `<CamelCased-EventName>Payload`. A future version may auto apply types to the `on_event` callback, so you not need cast. Moving on.

The first thing I noticed was that `evt` didn't seem to have a reference directly to the player I needed.
Bummer. No worries, it had the ID! With the ID, I then _guessed_ to inspect the global `game` class for assistance. `game` has a `get_player` method. `get_player` returns an instance of a `LuaPlayer`, which is a class instance with many methods and properties. We know that we _only_ want to speed up the player if he is in motion, so let's wrap our logic inside a conditional--`if (player.walking_state.walking) { ... }`.

After hunting and pecking around the API and the documentation, I concluded that there was not a direct value specifying a velocity of the character. However, I did come across `character_running_speed_modifier`, which our type definitions told me is of type `number`. Unexpectedly, through trial and error (typescript _did not help me here_), I learned that `character_running_speed_modifier` could be _0_! Of course `number`s can be zero, so defensive programmers everywhere are saying silently "you should have seen this coming", and they would be correct :). Equipped with this knowledge, let's fill in the rest.

```typescript
const onPositionChange = (evt: OnPlayerChangedPositionPayload) => {
const player = game.get_player(evt.player_index);
if (player.walking_state.walking) {
const lastSpeed = player.character_running_speed_modifier;
const baseSpeed = lastSpeed == 0 ? 1 : lastSpeed;
player.character_running_speed_modifier = baseSpeed * 1.01;
}
};
```

When our player stops running, we want to have to restart the acceleration process next time he runs.
My original approach was to listen for `keyup` events. Alas, Factorio has no key events. How else could I determine if the player has stopped running, and thus safely reset the speed modifier? Easy! On every tick of the game, I can check all players for motion--and turn of the multiplier if they are stopped!

```typescript
const onTick = (_evt: OnTickPayload) => {
Object.entries(game.players).forEach(([_id, player]) => {
if (
player.character_running_speed_modifier > 0 &&
!player.walking_state.walking
) {
player.character_running_speed_modifier = 0;
}
});
};
```

That is, for every player, if there is a positive speed modifier and and the player is not walking, zero out the modifier.

Why didn't we just do a `game.players.forEach`? `game.players` isn't an array, so that won't work. TypeScript protected us from making that mistake! What about `Object.entries(game.players).forEach`? Yep, that worked! Why did we do `Object.values(...)`? Because `game.tables` is a special dictionary in Lua. Not all iterables map cleanly between languages. `Object.values(...)` wouldn't have given us compiler error, but still would have failed. Improved typing is needed to protect against these failures. While TypeScriptToLua generally helps out a great deal protect us from writing erroneous code, it is only as good as its applied type constraints.

When using JS standard library provisions, as we did above, the compiler will add a typescript-lua polyfill, `lualib_bundle`. The emitted Lua perhaps is not as elegant:

```lua
require("lualib_bundle");
-- ...
onTick = function(_evt)
__TS__ArrayForEach(
__TS__ObjectEntries(game.players),
function(____, ____bindingPattern0)
local _id = ____bindingPattern0[1]
local player
player = ____bindingPattern0[2]
if (player.character_running_speed_modifier > 0) and (not player.walking_state.walking) then
player.character_running_speed_modifier = 0
end
end
)
end
```

Even with the extra boilerplate generated, the code is still quite readable. Could be cleaner! Surely as the TypeScriptToLua compiler evolves in time, it will be.

## Deploy it

I do not yet have clean, polished package & deploy scripts, but I do have functional ones.
Do your best to cover your eyes. Running `yarn install_mod` syncs the mod into my local Factorio installation. Coupled with a watch script, every code change immediately lands in the game folder, and requires only a scenario reload to get changes applied.

```json
"scripts": {
"clean:install": "rm -rf ~/Library/Application\\ Support/factorio/mods/vroom*",
"copy:install": "VERSION=$(cat info.json | jq -r .version) && rsync -r --exclude node_modules --exclude '.git*' --exclude build --exclude 'npm*' --exclude README.md --exclude package.json --exclude '*.lock' ./ ~/Library/Application\\ Support/factorio/mods/vroom_$VERSION/",
"install_mod": "run-s clean:install copy:install",
"watch": "chokidar 'locale/**/*.cfg' '**/*.lua' '**/*.json' -c 'yarn install_mod'"
}
```

Feel free to open an issue and collab on tidying the above in [create-factorio-mod](https://github.com/cdaringe/create-factorio-mod/).

## Enjoy it

<iframe width="560" height="315" src="https://www.youtube.com/embed/5cfHPHv6jS0" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
19 changes: 0 additions & 19 deletions docs/_posts/hello-world.md

This file was deleted.

0 comments on commit 6807c16

Please sign in to comment.