# Paint and patterns (custom.paint) ## What it is for `custom.paint` lets the player make things their own: colours and patterns on the character, on buildings they placed, on the vehicle they drive, on any entity the game marks as paintable. A paintable thing has named parts (`body`, `trim`, `roof`…); a look per part (a colour, a pattern, a second colour) is kept on the entity and saved with it. Palettes and patterns come from your project, and patterns can start locked and be unlocked by progress. The player saves looks as presets and applies them elsewhere. A paint screen picks the thing faced, its part and a swatch. A model with a paint mask keeps its texture and takes the colour only on its paintable panels. ## Turn it on Install the kit in a project and set its settings with the tools below; `gripforge kit api custom.paint` lists every setting, and `gripforge docs custom-paint` prints this guide offline. ```text gripforge_gamekit_install { project_id: "gkp_…", id: "custom.paint" } gripforge_gamekit_configure { project_id: "gkp_…", id: "custom.paint", config: { "reach": 6, "playerParts": ["body", "hat"], "maxPresets": 20 } } ``` ```json { "id": "hut", "kind": "building", "components": { "paintable": { "parts": ["walls", "roof"] } } } ``` ## First example A hut with paintable `walls` and `roof`. A colour is accepted on a known part, a locked pattern is refused until `unlockPattern` reveals it, and an unknown part, an unknown pattern or a malformed colour is refused. ```text custom.paint.paint(hut, "walls", { color: "#AA0000" }) // ok custom.paint.paint(hut, "walls", { pattern: "stripes", secondary: "#00AA00" }) // ok custom.paint.paint(hut, "roof", { pattern: "camo" }) // refused: camo is locked custom.paint.unlockPattern("camo") // paint.pattern_unlocked custom.paint.paint(hut, "roof", { pattern: "camo" }) // ok, paint.changed { part: "roof" } custom.paint.paint(hut, "door", { color: "#000000" }) // refused: not a part of the hut ``` ## Settings and defaults | Setting | Default | Meaning | | --- | --- | --- | | `reach` | 5 | the paint screen targets a paintable thing faced within this distance (metres, 1 to 30), else the player; it closes when the player walks further | | `paintPlayer` | `true` | {{s_paintPlayer}} | | `playerParts` | `["body"]` | {{s_playerParts}} | | `maxPresets` | 12 | {{s_maxPresets}} | ## Services - `custom.paint` (service): `palettes()`, `patterns()`, `paintables()`, `parts(entity)`, `look(entity)`, `paint(entity, part, { color?, pattern?, secondary? })` answers `{ ok, reason? }`, `copy(from, to)`, `unlockPattern(id)`, `presets()`, `savePreset(name, look)`, `applyPreset(entity, name)`, `target()`, `open(entity?)`, `close()` and `current()`. `entity` is an id or `"player"`. - Content: `paintable` is a component `{ parts: [...] }` (default `["body"]`) that marks an entity paintable; a mesh named after a part takes that part's colour, every other mesh takes the body colour. The `palettes` collection holds `{ id, name, colors }` with colours as `#rrggbb` (1 to 48 swatches). The `patterns` collection holds `{ id, name, texture?, unlocked? }`: `texture` is a variant of the `paint_pattern` asset slot, a tileable texture, and `unlocked: false` locks the pattern until it is unlocked. The look of each part is kept on the entity and saved with it. ## Events - `paint.changed`: a part of an entity got a new look (`entity`, `part`). - `paint.pattern_unlocked`: a locked pattern became available (`pattern`). - `paint.opened` (the paint screen opened on `entity`) and `paint.closed`. ## Hooks and modding - `paint.apply`: a part is about to take a look (`entity`, `part`, `color`, `pattern`, `secondary`, `source` of `paint`, `copy` or `preset`), after the kit accepted the paint, once per part; `color` may be changed to another `#rrggbb`, and cancelling keeps the old look and emits no `paint.changed` (`paint` answers the reason of the tap, `refused` by default). - `paint.unlock`: a locked pattern is about to become available (`pattern`); cancelled, it stays locked and no `paint.pattern_unlocked` is emitted. ```json { "id": "no_black_roofs", "hook": "paint.apply", "match": { "part": "roof", "color": "#000000" }, "cancel": true, "reason": "too_dark" } ``` ## Using it from content Script verbs: the actions `paint_entity` (`entity` as `player` or an id, `part`, `color`, `pattern`; defaults to the body of the player) and `unlock_pattern` (`pattern`). The input `paint_menu` (key `KeyP`) opens the paint screen. Variables: `paint.open`, `paint.patterns`. ```json { "type": "paint_entity", "entity": "player", "part": "body", "color": "#c0392b" } { "unlock_pattern": "stripes" } ``` ## When it does not do what you expect | Symptom | Cause and fix | | --- | --- | | `paint` answers `ok: false` | The entity is not paintable, the part is not one of its parts, the colour is not `#rrggbb`, the pattern is unknown or still locked, or a tap of `paint.apply` cancelled it. | | A mesh does not take the colour of its part | Name the mesh after the part (`roof`, `trim`…); a mesh with no part name takes the `body` colour. A model with a paint mask is tinted only on its masked panels. | | The paint screen targets the player, not the thing faced | The thing is further than `reach` or has no `paintable` component. | ## Limits - Looks are cosmetic colours and patterns on materials, not new meshes or decals. - The paint screen and the material tinting are web display features; on other targets the service, the verbs and the events work and the look is data your renderer applies. - The kit does not decide who may paint what: put rules in a `paint.apply` tap. ## Versions Kit `custom.paint` 1.2.0 (2026-10-10: the hooks `paint.apply` and `paint.unlock`; 1.1.0 added paint masks; 1.0.0 was the first release). It needs `input.actions` and `game.engine` ^1.