Nimbin[12]?Games / Entropie / commits / 5648cdf

Entropie git · main

Entropie festival game; first steps

sdl c game entropie cmake · first commit 2026-10-10 · last commit 2026-11-02 (17 hours ago) · synced 8 hours ago

C 88.5% Markdown 9.1% GLSL 2.3%
git clone https://git.christianimmanuel.de/games/Entropie.gitwget https://git.christianimmanuel.de/games/Entropie/archive/Entropie.tar.gz

entropie 1.56.0: careful people put tools back, practised ones cut even

Christian Immanuel · 2026-10-14 20:20 · 5648cdf579fc8b07a6a7244b2aba073915176025

- Done with a tool, careful people take it back to where it belongs (the
  tool room); careless ones leave it lying where they stopped
- The practised cut even pieces; the unpractised by eye (up to 0.2 m off)

Includes 1.55.0 (if not committed separately):
- Tools people shouldn't use (chainsaw unpractised, axe or saw drunk):
  used anyway with close calls; "Leave the chainsaw alone" in talk; proud,
  unwilling or drunk people may not listen
 entropie/Makefile                 |  2 +-
 entropie/README.md                |  7 ++++
 entropie/STATE.md                 |  9 +++--
 entropie/commit_message.txt       | 17 ++++-----
 entropie/src/people/ai.c          |  2 +
 entropie/src/people/ai.h          | 11 ++++++
 entropie/src/people/ai_help.c     | 42 ++++++++++++++++++++-
 entropie/src/people/ai_internal.h |  1 +
 entropie/src/people/ai_work.c     | 77 ++++++++++++++++++++++++++++++++++++++-
 entropie/src/people/talk.c        | 26 +++++++++++++
 entropie/src/world/object.h       |  2 +
 entropie/src/world/world.c        |  5 +++
 12 files changed, 186 insertions(+), 15 deletions(-)

diff --git a/entropie/Makefile b/entropie/Makefile
index 9b30905..49be6ab 100644
--- a/entropie/Makefile
+++ b/entropie/Makefile
@@ -1,5 +1,5 @@
 APP       := entropie
-VERSION   := 1.54.0
+VERSION   := 1.56.0
 
 PREFIX    ?= /usr
 BINDIR    ?= $(PREFIX)/bin
diff --git a/entropie/README.md b/entropie/README.md
index a1f5f35..54ecc99 100644
--- a/entropie/README.md
+++ b/entropie/README.md
@@ -358,6 +358,13 @@ say no more often and don't come to you for a bit.
   or drunk ones just throw it on, and a high loose pile comes down now and then
   If you keep the axe, the bold ones ask for it, the others wait until you
   put it down
+- The practised cut even pieces, the others by eye. Careful people take
+  the tools back to the tool room when they're done; the others leave them
+  lying where they stopped
+- Tools they shouldn't use (the chainsaw without practice, the axe or saw
+  drunk) they use anyway, with a remark and now and then a close call. Talk
+  to them: "Leave the chainsaw alone" - proud, unwilling or drunk ones may
+  not listen
 - Showing where the woodpile goes: they follow you, a glowing frame on the
   ground shows the spot, and a left click puts the woodpile there
 - Trash: they take a garbage bag, bag piece after piece and empty full bags
diff --git a/entropie/STATE.md b/entropie/STATE.md
index 671d1b0..0579678 100644
--- a/entropie/STATE.md
+++ b/entropie/STATE.md
@@ -39,6 +39,10 @@ Worn out: "worn out" shows until energy is back above 30%; no running until then
 
 ## Behaviours (people on their own)
 
+Taking care: the practised cut even pieces (0.6 m), the unpractised by eye, up to 0.2 m off (firewood skill; `ai_work.c` ai_taskFor). Done with a tool (idle, wandering), careful people (`ai_stackCare` 0.5 and up) take it back to where it was at the start (`has_home`, the tool room, up to 80 m), the others put it down where they are (`ai_help.c` ai_putAway).
+
+Tools they shouldn't use (`ai_work.c`, Ai_shouldntUse): the chainsaw with tree skill under 0.4, the axe or chainsaw drunk over 0.6. They use them anyway: a remark when they start, a close call (a stagger, "it kicked back") about every 8 s of work with it. Talking to a worker on a wood job: "Leave the <tool in hand, else chainsaw> alone" / "You can use the ... again". Told, they put it down and take another tool; they ignore it with a chance of 0.1 + 0.45 pride + 0.5 (0.5 - willing) + 0.4 drunk ("Pfft. I know what I'm doing").
+
 | Mode | What they do |
 |---|---|
 | Idle / wander | stand about (fidgets, gestures), stroll near home |
@@ -134,8 +138,7 @@ Most important first.
 - More gestures and feelings to pick personalities from (a list to choose from)
 
 **People**
-- Tools they shouldn't use: they use them anyway, unless told (the ignorant don't listen)
-- The skilled take care: clean tools and area, sort wood by length, cut even pieces
+- The skilled take care, still to do: sort wood by length on the pile, sweep up chips and twigs
 - Peeing properly: open the fly; women crouch and pull down / lift the skirt or dress; Urinella (some don't like it; mishaps; tidy, careless or high disposal)
 - Clothing (trousers, skirt, dress, fly) – needed for the above
 
@@ -151,4 +154,4 @@ Most important first.
 - The walking map rebuild takes ~9 ms when fixed things change (a felled tree)
 
 ---
-State of `entropie 1.54.0 (build 5b985f0)`
+State of `entropie 1.56.0 (build efaf3bb)`
diff --git a/entropie/commit_message.txt b/entropie/commit_message.txt
index 61700eb..e66251c 100644
--- a/entropie/commit_message.txt
+++ b/entropie/commit_message.txt
@@ -1,11 +1,10 @@
-entropie 1.54.0: no more elbow jumps folding the arms on an edge
+entropie 1.56.0: careful people put tools back, practised ones cut even
 
-- Limbs between folded and stretched blend the two fits (no jump at the
-  switch): elbows on an edge popped up to 31 cm, now none
-- An elbow swung up over an edge stays solved that way, folded or not
-- Just taken hold of an edge: the elbow eases back in, the hand stays
+- Done with a tool, careful people take it back to where it belongs (the
+  tool room); careless ones leave it lying where they stopped
+- The practised cut even pieces; the unpractised by eye (up to 0.2 m off)
 
-Includes 1.53.0 (if not committed separately):
-- Climbing: legs stay out of what you climb (the 0.8 m walk-in climb no
-  longer puts the thigh into it); feet keep room for the knee at walls
-- Raised ladder aimed too low: leans as usual instead of refusing
+Includes 1.55.0 (if not committed separately):
+- Tools people shouldn't use (chainsaw unpractised, axe or saw drunk):
+  used anyway with close calls; "Leave the chainsaw alone" in talk; proud,
+  unwilling or drunk people may not listen
diff --git a/entropie/src/people/ai.c b/entropie/src/people/ai.c
index 6dcfcad..a33fd88 100644
--- a/entropie/src/people/ai.c
+++ b/entropie/src/people/ai.c
@@ -1150,6 +1150,8 @@ void Ai_update(Object *self, Object *const *objects, int count, float dt) {
             stand(self);
         }
     }
+    /* Done with a tool: careful people put it back where it belongs, the others leave it lying */
+    if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER) && ai_putAway(self, objects, count, dt)) return;
     /* Tidy people pick things up on the way */
     if ((ai->mode == AI_IDLE || ai->mode == AI_WANDER || (ai->mode == AI_HELP && !ai->pushing && !ai->copied) ||
          ai->mode == AI_SUPPLY || ai->mode == AI_FETCH) && ai_tidyUp(self, objects, count, dt))
diff --git a/entropie/src/people/ai.h b/entropie/src/people/ai.h
index f596e17..bc5f7b8 100644
--- a/entropie/src/people/ai.h
+++ b/entropie/src/people/ai.h
@@ -160,6 +160,10 @@ typedef struct {
     bool           tool_asked;  /* asked the player for their axe (once per order) */
     float          tidy_pause;  /* bending down for trash on the way (seconds left) */
     bool           tool_waiting; /* waiting for the player to put the axe down */
+    unsigned char  banned;      /* tools (1 << ToolKind) they were told to leave alone ... */
+    unsigned char  defies;      /* ... and those they use anyway ("I know what I'm doing") */
+    unsigned char  warned;      /* tools they already said something about (using one they shouldn't) */
+    float          risky;       /* seconds until the next close call with a tool they shouldn't use */
     float          speech_time;
     float          sniff_cooldown; /* until the next remark about a smell */
     AiMode         before;      /* mode to go back to after being shown something */
@@ -194,6 +198,13 @@ void Ai_setWoodpile(struct Object *self, struct Object *woodpile);
 /* In a conversation or not; done = it ended (no new chat for a while). */
 void Ai_setTalking(struct Object *self, bool talking);
 
+/* Tools: a tool someone shouldn't use (the chainsaw unpractised, the axe or
+   the saw drunk) they use anyway, unless told not to - and the ignorant
+   don't listen. tool: a ToolKind. Ai_banTool: true = they'll leave it. */
+bool Ai_shouldntUse(const struct Object *self, int tool);
+bool Ai_banTool(struct Object *self, int tool, bool ban);
+bool Ai_mayUse(const struct Object *self, int tool);
+
 /* Time of day (hours) for everybody's plans. */
 void Ai_setHour(float hour);
 /* Does this job need doing right now (not much firewood, trash lying around, dark and no fire)? */
diff --git a/entropie/src/people/ai_help.c b/entropie/src/people/ai_help.c
index ebd2278..de18de0 100644
--- a/entropie/src/people/ai_help.c
+++ b/entropie/src/people/ai_help.c
@@ -110,7 +110,7 @@ Object *ai_findTool(const Object *self, Object *const *objects, int count, Job j
     float best_dist = 60.0f; /* (the tool room may be a walk away) */
     for (int i = 0; i < count; i++) {
         Object *o = objects[i];
-        if (o->is_held || !Tools_canDo(toolOf(o), job)) continue;
+        if (o->is_held || !Tools_canDo(toolOf(o), job) || !Ai_mayUse(self, toolOf(o))) continue;
         if (ai_skipped(&self->ai, o)) continue; /* couldn't get at it */
         float d = flatDistance(o->position, self->position);
         if (d < best_dist) {
@@ -261,6 +261,46 @@ bool ai_tidyUp(Object *self, Object *const *objects, int count, float dt) {
     return false;
 }
 
+#define PUT_AWAY_CARE 0.5f   /* this careful (see ai_stackCare): tools go back where they belong */
+#define PUT_AWAY_FAR  80.0f  /* ... unless that's further than this */
+
+/* Holding a tool with nothing to do with it: careful people take it back to
+   its place (the tool room), the others put it down where they are. */
+bool ai_putAway(Object *self, Object *const *objects, int count, float dt) {
+    Ai *ai = &self->ai;
+    int hand = -1;
+    for (int h = 0; h < 2; h++) {
+        const Object *it = self->hands.item[h];
+        if (self->hands.state[h] == HAND_HOLDING && it && it->has_home &&
+            (it->type == OBJ_AXE || it->type == OBJ_SHEARS || it->type == OBJ_CHAINSAW)) hand = h;
+    }
+    if (hand < 0 || ai->talking) return false;
+    Object *tool = self->hands.item[hand];
+    float d = flatDistance(tool->home_spot, self->position);
+    if (ai_stackCare(self) < PUT_AWAY_CARE || d > PUT_AWAY_FAR) { /* down it goes, right here */
+        Hands_drop(self, hand);
+        return false;
+    }
+    if (d > 0.7f) {
+        Vec3 dir = ai_steer(self, tool, objects, count, tool->home_spot);
+        if (vec3_length(dir) < 0.5f) {
+            stand(self);
+            if (isStuck(ai, d, dt) && ai->stuck > 6.0f) Hands_drop(self, hand); /* (can't get there: here then) */
+        } else {
+            ai_walk(self, dir, WALK_SPEED * 0.8f, dt);
+        }
+        return true;
+    }
+    stand(self);
+    Hands_drop(self, hand);
+    tool->position = tool->home_spot; /* (back on its hook, as it was) */
+    tool->rotation = tool->home_rot;
+    tool->velocity = tool->angular_velocity = vec3_zero();
+    Object_syncColliders(tool);
+    if (random01(ai) < 0.3f) Ai_say(self, "Back where it belongs.");
+    return true;
+}
+
 /* ---------- Doing what the leader does ---------- */
 
 /* What sort of thing it is, for "something like that" (0: nothing to copy). */
diff --git a/entropie/src/people/ai_internal.h b/entropie/src/people/ai_internal.h
index dffdf23..0fbc962 100644
--- a/entropie/src/people/ai_internal.h
+++ b/entropie/src/people/ai_internal.h
@@ -87,6 +87,7 @@ float ai_stackCare(const Object *self);
 void  ai_pileSlid(Object *self, int slid);
 void ai_updateFollow(Object *self, Object *const *objects, int count, float dt);
 bool ai_tidyUp(Object *self, Object *const *objects, int count, float dt);
+bool ai_putAway(Object *self, Object *const *objects, int count, float dt);
 void ai_updateHelp(Object *self, Object *const *objects, int count, float dt);
 void ai_ensureReady(Object *self);
 
diff --git a/entropie/src/people/ai_work.c b/entropie/src/people/ai_work.c
index 0316c9b..b601b44 100644
--- a/entropie/src/people/ai_work.c
+++ b/entropie/src/people/ai_work.c
@@ -7,6 +7,8 @@
 
 /* ---------- Wood work ---------- */
 
+#define UNEVEN_CUT 0.2f /* m a piece may come out longer or shorter, cut by someone unpractised */
+
 /* Where to stand for a job on o, and what to face. */
 WoodTask ai_taskFor(const Object *self, Object *o, Job job) {
     WoodTask t = { .job = job, .target = o };
@@ -34,6 +36,12 @@ WoodTask ai_taskFor(const Object *self, Object *o, Job job) {
         u = vec3_length(u) > 1e-3f ? vec3_normalize(u) : vec3(1.0f, 0.0f, 0.0f);
         Vec3 v = vec3_normalize(vec3_cross(u, UP));
         float along = job == JOB_CUT ? Tools_cutPoint(o, self->position) : length * 0.5f;
+        if (job == JOB_CUT) { /* the practised cut even pieces; the others by eye, some longer, some shorter */
+            unsigned h = (unsigned)(int)(o->position.x * 731.0f) * 2654435761u ^ (unsigned)(int)(o->position.z * 557.0f) * 40503u;
+            float r = (float)((h >> 8) & 1023u) / 1023.0f * 2.0f - 1.0f;
+            along += r * UNEVEN_CUT * (1.0f - self->skill[ORDER_FIREWOOD]);
+            along = clampf(along, 0.3f, length - 0.3f);
+        }
         Vec3 point = vec3_add(a, vec3_scale(axis, along));
         float side = vec3_dot(vec3_sub(self->position, point), v) < 0.0f ? -1.0f : 1.0f;
         float reach = o->colliders[0].half_extents.x;
@@ -86,12 +94,78 @@ bool ai_nextWoodTask(Object *self, Object *const *objects, int count, Vec3 area,
     return true;
 }
 
+/* ---------- Tools they shouldn't use ---------- */
+
+#define SAW_PRACTICE  0.4f   /* tree skill below this: the chainsaw is too much for them */
+#define AXE_DRUNK     0.6f   /* this drunk: hands off the axe and the saw */
+#define CLOSE_CALL    8.0f   /* about this many seconds of work between close calls with one */
+
+bool Ai_shouldntUse(const Object *self, int tool) {
+    if (tool == TOOL_CHAINSAW && self->skill[ORDER_TREES] < SAW_PRACTICE) return true;
+    if ((tool == TOOL_AXE || tool == TOOL_CHAINSAW) && self->drunk > AXE_DRUNK) return true;
+    return false;
+}
+
+bool Ai_mayUse(const Object *self, int tool) {
+    unsigned bit = 1u << tool;
+    return !(self->ai.banned & bit) || (self->ai.defies & bit);
+}
+
+/* How likely they ignore being told: proud, unwilling, drunk. */
+static float ignorance(const Object *self) {
+    return clampf(0.1f + 0.45f * self->temperament.pride + 0.5f * (0.5f - self->ai.character.willing)
+                  + 0.4f * fminf(self->drunk, 1.0f), 0.0f, 0.9f);
+}
+
+bool Ai_banTool(Object *self, int tool, bool ban) {
+    unsigned bit = 1u << tool;
+    Ai *ai = &self->ai;
+    if (!ban) {
+        ai->banned &= (unsigned char)~bit;
+        ai->defies &= (unsigned char)~bit;
+        return true;
+    }
+    ai->banned |= (unsigned char)bit;
+    if (random01(&self->ai) < ignorance(self)) { /* doesn't listen */
+        ai->defies |= (unsigned char)bit;
+        return false;
+    }
+    ai->defies &= (unsigned char)~bit;
+    for (int h = 0; h < 2; h++) /* put it down right here */
+        if (self->hands.state[h] == HAND_HOLDING && toolOf(self->hands.item[h]) == (ToolKind)tool) Hands_drop(self, h);
+    if (ai->tool && toolOf(ai->tool) == (ToolKind)tool) ai->tool = NULL;
+    return true;
+}
+
+/* Working with a tool they shouldn't use: a word about it first, and now
+   and then a close call (it kicks back, the axe glances off): a stagger. */
+static void riskyTool(Object *self, ToolKind held, float dt) {
+    Ai *ai = &self->ai;
+    if (!Ai_shouldntUse(self, held)) return;
+    unsigned bit = 1u << held;
+    if (!(ai->warned & bit)) {
+        ai->warned |= (unsigned char)bit;
+        ai->risky = CLOSE_CALL * (0.5f + random01(&self->ai));
+        Ai_say(self, self->drunk > AXE_DRUNK ? "I'm fine, I'm fine! Watch this..."
+                     : held == TOOL_CHAINSAW ? "Never used one of these... how hard can it be?" : "Here goes nothing.");
+        return;
+    }
+    if (!Tools_isWorking(self) || (ai->risky -= dt) > 0.0f) return;
+    ai->risky = CLOSE_CALL * (0.5f + random01(&self->ai));
+    Body_stagger(&self->body, vec3_scale(Object_facing(self), -0.8f));
+    Ai_say(self, held == TOOL_CHAINSAW ? "Whoa! It kicked back at me!" : "Whoa, that one glanced off!");
+}
+
 /* Holds a tool that can do the job, fetching one if needed.
    0 = ready, 1 = busy getting it, -1 = there is none. */
 static int getTool(Object *self, Object *const *objects, int count, Job job, float dt) {
     Ai *ai = &self->ai;
     int hand;
     ToolKind held = Tools_held(self, &hand);
+    if (held >= TOOL_AXE && !Ai_mayUse(self, held) && hand >= 0) { /* told to leave that one: down it goes */
+        Hands_drop(self, hand);
+        return 1;
+    }
     bool saw_ok = held != TOOL_CHAINSAW || (hand >= 0 && self->hands.state[1 - hand] == HAND_FREE);
     if (Tools_canDo(held, job) && saw_ok) return 0;
     if (held == TOOL_CHAINSAW && Tools_canDo(held, job)) { /* both hands for the saw */
@@ -136,6 +210,7 @@ void ai_workOn(Object *self, Object *const *objects, int count, const WoodTask *
     int tool = getTool(self, objects, count, t->job, dt);
     if (tool < 0) { skipTarget(ai, t->target); stand(self); return; }
     if (tool > 0) return;
+    riskyTool(self, Tools_held(self, NULL), dt);
     if (self->hands.state[0] == HAND_REACHING || self->hands.state[1] == HAND_REACHING) { stand(self); return; }
 
     float dist = flatDistance(t->spot, self->position);
@@ -724,7 +799,7 @@ void ai_updateWork(Object *self, Object *const *objects, int count, float dt) {
     if (ai->order != ORDER_FIRE && takeBreak(self, objects, count, dt)) return; /* (tending a fire they sit by it anyway) */
     if (ai->order == ORDER_TRASH) { workTrash(self, objects, count, dt); return; }
     if (ai->order == ORDER_FIRE) { workFire(self, objects, count, dt); return; }
-    if (Tools_isWorking(self)) { stand(self); return; }
+    if (Tools_isWorking(self)) { riskyTool(self, Tools_held(self, NULL), dt); stand(self); return; }
 
     /* Lively people working near the player like company */
     Object *player = findPlayer(objects, count);
diff --git a/entropie/src/people/talk.c b/entropie/src/people/talk.c
index db038ca..1b4ec0a 100644
--- a/entropie/src/people/talk.c
+++ b/entropie/src/people/talk.c
@@ -1,5 +1,6 @@
 #include "people/talk.h"
 #include "world/camp.h"
+#include "world/tools.h"
 
 #include <stdio.h>
 #include <string.h>
@@ -198,6 +199,12 @@ static bool handOver(Object *player, Object *npc, Job job) {
     return Hands_receive(npc, npc->main_hand, tool);
 }
 
+/* The tool a worker's talk is about: the one in their hand, else the chainsaw. */
+static int talkTool(const Object *npc) {
+    ToolKind held = Tools_held(npc, NULL);
+    return held == TOOL_AXE || held == TOOL_CHAINSAW ? (int)held : (int)TOOL_CHAINSAW;
+}
+
 static void answer(Talk *talk, World *world, Object *player, int choice) {
     Object *npc = talk->with;
     switch (talk->node) {
@@ -238,6 +245,17 @@ static void answer(Talk *talk, World *world, Object *player, int choice) {
         else if (choice == 2 && woodJob(npc->ai.order)) { Ai_show(npc, SHOW_PLACE, player); wrapUp(talk, "Lead the way."); }
         else if (choice == 2) { Ai_stopWork(npc); go(talk, NODE_TASKS); }
         else if (choice == 3) { Ai_stopWork(npc); Ai_help(npc, player); wrapUp(talk, "Sure!"); }
+        else if (choice == 5) {
+            int tool = talkTool(npc);
+            if (!(npc->ai.banned & (1u << tool))) {
+                bool obeys = Ai_banTool(npc, tool, true);
+                wrapUp(talk, obeys ? (shy(npc) ? "Oh... okay, sorry." : "Okay, I'll leave it.")
+                                   : npc->temperament.pride > 0.3f ? "Pfft. I know what I'm doing." : "Yeah, yeah...");
+            } else {
+                Ai_banTool(npc, tool, false);
+                wrapUp(talk, "Alright!");
+            }
+        }
         else { Ai_stopWork(npc); wrapUp(talk, "Thanks, I could use one."); }
         break;
     case NODE_HOW:
@@ -380,5 +398,13 @@ int Talk_choices(const Talk *talk, const char *out[TALK_CHOICES]) {
     for (int k = 0; k < TALK_CHOICES; k++)
         if (CHOICES[talk->node][k]) out[n++] = CHOICES[talk->node][k];
     if (talk->node == NODE_WORKER && !woodJob(talk->with->ai.order)) out[1] = "Do something else instead";
+    if (talk->node == NODE_WORKER && woodJob(talk->with->ai.order) && n < TALK_CHOICES) { /* about the tool they use */
+        static char line[64];
+        const Object *npc = talk->with;
+        int tool = talkTool(npc);
+        snprintf(line, sizeof line, npc->ai.banned & (1u << tool) ? "You can use the %s again" : "Leave the %s alone",
+                 tool == TOOL_CHAINSAW ? "chainsaw" : "axe");
+        out[n++] = line;
+    }
     return n;
 }
diff --git a/entropie/src/world/object.h b/entropie/src/world/object.h
index 0b3ab8a..c979827 100644
--- a/entropie/src/world/object.h
+++ b/entropie/src/world/object.h
@@ -101,6 +101,8 @@ typedef struct Object {
     Vec3       position;      /* base point (bottom center) */
     Vec3       rotation;      /* Euler radians, applied Y, X, Z */
     Vec3       pivot;         /* mesh origin relative to position */
+    bool       has_home;      /* tools: where they belong (the tool room), put back there by careful people */
+    Vec3       home_spot, home_rot;
     Mesh       mesh;          /* empty for characters, they are drawn as figures */
 
     Collider   colliders[OBJECT_MAX_COLLIDERS];
diff --git a/entropie/src/world/world.c b/entropie/src/world/world.c
index d266f65..201fffc 100644
--- a/entropie/src/world/world.c
+++ b/entropie/src/world/world.c
@@ -712,6 +712,11 @@ bool World_add(World *world, Object *obj) {
         world->objects  = grown;
         world->capacity = cap;
     }
+    if (!obj->has_home && (obj->type == OBJ_AXE || obj->type == OBJ_SHEARS || obj->type == OBJ_CHAINSAW)) {
+        obj->has_home = true; /* (where it is at first is where it belongs) */
+        obj->home_spot = obj->position;
+        obj->home_rot = obj->rotation;
+    }
     world->objects[world->count++] = obj;
     return true;
 }