Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

a way to copy many almost identical object?

Hello everyone,

I'm currently trying to add NPCs to my game nothing to difficult right? but I don't want the NPCs to be the same on each game so I did something like that (keep in mind that it's a shortened version)
<<set $NPC to {
	name: either("Lara", "Amelia"),
	eye: either("blue", "green", "brown"),
	hair: either("red", "blonde", "brown", "black"),
}>>

Now I don't want to have to rewrite all this code every time since all my NPC are not created at the start of the game.

Do you have suggestions for me? I tried a couple of things but beside setting them all at the start ( I fear it would make the launch unnecessarily long) or write the code at the start of the passage the npc is created I have no idea. Idea that works at least.
«1

Comments

  • You can use a couple of different techniques:

    1. A Javascript Object Constructor to create each of your NPCs:
    Add the following to your script passage, it creates an NPC object constructor which you can use to create NPC objects with random attributes.
    window.NPC = function () {
    	this.name = either("Lara", "Amelia");
    	this.eye = either("blue", "green", "brown");
    	this.hair = either("red", "blonde", "brown", "black");
    };
    

    2. By using SugarCube's built in clone method

    The following passage shows how to use both of the above techniques:
    <<set $npc1 to new NPC()>>
    <<set $npc2 to new NPC()>>
    npc1: <<print $npc1.name>>
    npc2: <<print $npc2.name>>
    
    <<set $npc3 to clone($npc1)>>
    npc3: <<print $npc3.name>>
    
    Change npc3's name and see that npc1 and npc3 are not the same npc.
    <<set $npc3.name to "some other name">>
    npc1: <<print $npc1.name>>
    npc3: <<print $npc3.name>>
    
  • edited June 2015
    If you're using SugarCube you can also put that code in the widgets passage and just call the widget whenever you want it.
  • @Claretta
    I would like to see that done. is it just as simple as
    <<widget "NPC">>
    <</widget>>
  • edited June 2015
    Yes.
    <<widget "NPC">><<set $NPC to {
    	name: either("Lara", "Amelia"),
    	eye: either("blue", "green", "brown"),
    	hair: either("red", "blonde", "brown", "black")
    }>><</widget>>
    

    Then you just write <<NPC>> in passages where you want it.
  • Thank you!

    What I have to realize is that to learn SugarCube I should go back to Twine 1/SugarCube tutorials and that they still work over here in Twine 2 (with a few exceptions of course).
  • Thank you both @greyelf and @Claretta you once again help me

    I'll go with Claretta's suggestion for this one since i'm on SugarCube and the widget is a bit more intuitive to use but i'll remember your suggestion greyelf if i use something else than sugarcube.

    Once again both of you thank you.

  • @Claretta

    My NPC Widget returns nothing.

    Do I need to <<print Args[]>> or something?
  • edited June 2015
    Claretta's widget example doesn't output anything. It only contains a <<set>> macro, which sets the value of the $NPC variable.

    If your NPC widget is similar, then….
  • edited June 2015
    So if I want to reflect the choices (aka use the Macro)... it isn't just <<NPC>> ?
  • Each time you call the above <<NPC>> widget it is changing the current value of the $NPC variable so if you want to keep the current value of the $NPC variable then you will need to copy that value into a second variable.
    /% Generate a random NPC %/
    <<NPC>>
    <<print $NPC.name>>
    
    /% Copy $NPC's current value into a second variable to keep it.
    <<set $second to clone($NPC)>>
    
    /% Generate a different random NPC %/
    <<NPC>>
    <<print $NPC.name>>
    <<print $second.name>>
    
  • edited June 2015
    If we're talking about something like Claretta's example, then using the widget has nothing to do with reflecting choices, its sole use is to set $NPC variable to a new random NPC. If you wanted to do both, then you'd either have to make the widget do both or output the NPC's details separately (the latter probably being the better option).

    For example, continuing with Claretta's example:
    <<widget "NPC">><<set $NPC to {
    	name: either("Lara", "Amelia"),
    	eye: either("blue", "green", "brown"),
    	hair: either("red", "blonde", "brown", "black")
    }>><</widget>>
    
    To use it to setup a new random NPC and then output details about that NPC, you could do something like the following:
    <<NPC>>\
    Hello there, $NPC.name.  I have a confession to make, I've always secretly envied your bright $NPC.eye eyes and luxuriant $NPC.hair hair.
    

    Though, you probably wouldn't want to use the widget as-is, since it offers no protection against making NPCs with the exact same name, but differing in other details (i.e. it would be odd if Lara had brown eyes the first time you met her, but green the next).
  • edited June 2015
    I didn't do anything fancy but copy the code the original poster gave and stick it in a widget.

    I agree with TME that the example code is incomplete in this instance.
  • So then, assuming I start off with a million different choices in my (either), I am still not protected because either means exactly that: either. So I can still get two Lara's with different features.

    So is there a way to have it remove an option from that list of choices once it has been selected? Or is that non-trivial?
  • edited June 2015
    There's a way to remove names from arrays, yes. I don't know how to do it. Though you could also go <<if $NPC.name is "Lara">><<NPC>>

    That will reroll the <<NPC>> widget if you get a Lara. If you set the widget up right, you could set it up in an <<elseif>> loop, so that it constantly rerolls until it gets a valid name.

    Though you'd also need a way to record chosen NPC names as they are chosen, so you could do something like
    After names are chosen:
    
    <<NPC>>
    <<if $NPC.name is "Lara">>
    <<set $Lara to true>>
    
    
    Before names are chosen:
    
    <<if $Lara is true>>
    <<if $NPC.name is "Lara">>
    <<NPC>>
    


    No guarantees that this will be the best solution, as I'm prone to inventive but meandering code. For small arrays though, like only 3 name choices, it would work well enough.
  • You could create an array of the possible names and the remove each one as it is used, thus you would not end up with two NPC's with the same name.

    note: the following example uses the Javascript code from this post to handle the finding and removing of an array element from the $nameList array.

    Try the following StoryInit and Start passages:
    :: StoryInit
    <<set $nameList to ["name one", "name two", "name three"]>>
    
    <<widget "NPC">>
    	<<set $NPC to {
    		name: either($nameList),
    		eye: either("blue", "green", "brown"),
    		hair: either("red", "blonde", "brown", "black")
    	}>>
    	<<run $nameList.removeElement($NPC.name)>>
    <</widget>>
    
    :: Start
    original name list: <<print $nameList>>
    
    <<NPC>>
    npc name: <<print $NPC.name>>
    current name list: <<print $nameList>>
    
  • edited June 2015
    I understand it thus far, @GreyElf... but now that there is a new instance of nameList, how do I access it again? Is it just <<NPC>> to recall it with a new selection?

    AH..... yes. That DOES work.

    Thank you for being amazing!
  • edited June 2015
    Here is everything in one place in case someone else would like to use it.

    ::Start::
    Name List: <<print $nameList>>\
    <<NPC>>\
    Character 1 is named $NPC.name. $NPC.name has $NPC.eyes eyes and $NPC.hair hair.\
    <<NPC>>\
    Character 2 is named $NPC.name. $NPC.name has $NPC.eyes eyes and $NPC.hair hair.\
    <<NPC>>\
    Character 3 is named $NPC.name. $NPC.name has $NPC.eyes eyes and $NPC.hair hair.\
    

    ::StoryInit::
    <<set $nameList to ["Lara", "Amelia", "John"]>>\
    <<widget "NPC">>
    	<<set $NPC to {
    		name: either($nameList),
    		eyes: either("blue", "green", "brown"),
    		hair: either("red", "blonde", "brown", "black")
    	}>>
    	<<run $nameList.removeElement($NPC.name)>>
    <</widget>>\
    

    Javascript
    /* MDN Polyfill */
    if (!Array.prototype.indexOf) {
    	Array.prototype.indexOf = function(searchElement, fromIndex) {
    		var k;
    
    		if (this == null) {
    			throw new TypeError('"this" is null or not defined');
    		}
    
    		var O = Object(this);
    
    		var len = O.length >>> 0;
    
    		if (len === 0) {
    			return -1;
    		}
    
    		var n = +fromIndex || 0;
    
    		if (Math.abs(n) === Infinity) {
    			n = 0;
    		}
    
    		if (n >= len) {
    			return -1;
    		}
    
    		k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
    
    		while (k < len) {
    			if (k in O && O[k] === searchElement) {
    				return k;
    			}
    			k++;
    		}
    		return -1;
    	};
    }
    
    if (!Array.prototype.removeElement) {
    	Array.prototype.removeElement = function(element) {
    		if (this == null) {
    			throw new TypeError('"this" is null or not defined');
    		}
    
    		var i = this.indexOf(element)
    		if (i != -1) {
    			this.splice(i,1);
    		}
    		return this;
    	};
    }
    

    It will output:
    Name List: Lara,Amelia,John

    Character 1 is named John. John has brown eyes and brown hair.

    Character 2 is named Amelia. Amelia has green eyes and brown hair.

    Character 3 is named Lara. Lara has brown eyes and blonde hair.
  • My guess is that if you now add clone() you can save them off and reuse them as you need to.... Though I haven't tried it.
  • edited June 2015
    /sigh

    1. You do not need to polyfill ES5 (or below) native methods in any SugarCube version. You do not need to polyfill ES6 (or below) native methods in SugarCube 2.x. TL;DR: It's beyond pointless to polyfill Array.prototype.indexOf() in SugarCube because there's already a polyfill for it (for, you know, the one browser that needs it).

    2. Rather than using the either() function and your Array.prototype.removeElement() method, why not simply use SugarCube's Array.prototype.pluck() method (2.x and 1.x)? For example:
    <<set $nameList to ["Lara", "Amelia", "John"]>>\
    <<widget "NPC">>\
    	<<set $NPC to {
    		name: $nameList.pluck(),
    		eyes: either("blue", "green", "brown"),
    		hair: either("red", "blonde", "brown", "black")
    	}>>\
    <</widget>>
    
  • edited June 2015
    Rather than using the either() function and your Array.prototype.removeElement() method, why not simply use SugarCube's Array.prototype.pluck() method (2.x and 1.x)? For example:

    Why? ha! Because until you mentioned it I had NO IDEA what it was!

    Let me give it a try right now.
  • edited June 2015
    Wow. Okay so that means I didn't need ANY JavaScript in there at all. Nice! It simplifies things a LOT.

    So is there a way to save off each person as a new entity?
  • Forget it. I think THIS worked:
    <<set $first to $NPC>>
    

    So then, like this:
    Name List: <<print $nameList>>
    <<NPC>>\
    Character 1 is named $NPC.name. $NPC.name has $NPC.eyes eyes and $NPC.hair hair.\
    <<set $first to $NPC>>
    <<NPC>>\
    Character 2 is named $NPC.name. $NPC.name has $NPC.eyes eyes and $NPC.hair hair.\
    <<set $second to $NPC>>
    <<NPC>>\
    Character 3 is named $NPC.name. $NPC.name has $NPC.eyes eyes and $NPC.hair hair.\
    <<set $third to $NPC>>
    
    
    //JUST A TEST TO MAKE SURE
    $first.name 
    $second.name 
    $third.name
    
  • Yes, that will work fine. You can also spin that around a bit and do it like this:
    Name List: <<print $nameList>>
    <<NPC>><<set $first to $NPC>>\
    Character 1 is named $first.name. $first.name has $first.eyes eyes and $first.hair hair.
    <<NPC>><<set $second to $NPC>>\
    Character 2 is named $second.name. $second.name has $second.eyes eyes and $second.hair hair.
    <<NPC>><<set $third to $NPC>>\
    Character 3 is named $third.name. $third.name has $third.eyes eyes and $third.hair hair.
    
  • edited June 2015
    Something I suppose that I should have mentioned before.

    Regardless of how you're doing it, if you are removing entries from the list of names, then you'll need to ensure that you do not call for a new random NPC more times than you have names or things will get weird (because there will be no names left to use). Simply being vigilant works, or you could pre-generate your NPCs, or you could employ an NPC cache, or….

    Just something to keep in mind.
  • Holy carp that's awesome, compact, and easier all the way around.

    (and yes, carp was intentional.... seemed funnier that way)
  • 1. You do not need to polyfill ES5 (or below) native methods in any SugarCube version.
    The other post containing the Javascript was original written for Harlowe, and I included the Mozilla Developer Network's version of the indexOf() polyfill because they suggest it was a good idea to do so.

    It was my own laziness that I did not search to see if the story format in question has implemented its own version of a method for array element remove.
  • greyelf wrote: »
    The other post containing the Javascript was original written for Harlowe, and I included the Mozilla Developer Network's version of the indexOf() polyfill because they suggest it was a good idea to do so.
    My reply was in reference to Sage's post. Yes, the original idea may have come from you in another post (in a different thread), but don't borrow trouble when you don't need to (also, as noted below, my post wasn't about affixing blame, merely fixing the issue).

    Also, if by "they" you mean MDN (on its page there), then no, they do not suggest the use of the polyfill. They simply provide it and note that some browsers may need it. It may seem like I'm splitting hairs here, but the distinction is real and important. Beyond that, AFAIK, Harlowe, like SugarCube 2.x, includes polyfills for ES5 and ES6*, so it shouldn't need it either.

    * Native object methods only. The new syntax in ES6 would require a transpiler, which SugarCube 2.x does not include, and I don't think Harlowe does either (though, admittedly, I could be wrong about that; I don't claim to be an expert on Harlowe).

    greyelf wrote: »
    It was my own laziness that I did not search to see if the story format in question has implemented its own version of a method for array element remove.
    Tut tut, there's no need for that. It was not my intention to lambaste anyone. I was just pointing out that polyfills are unnecessary with SugarCube.

    Yes, I probably showed my exasperation with the situation more than I needed to (which is to say, at all), but it just begins to wear on you when no one bothers to read the docs.
  • Also, you could change the widget to allow an optional name argument. For example:
    <<widget "NPC">>\
    	<<set $NPC to {
    		name : $args[0] || $nameList.pluck(),
    		eyes : either("blue", "green", "brown"),
    		hair : either("red", "blonde", "brown", "black")
    	}>>\
    <</widget>>
    
    That done, you could do things like the following:
    <<set $nameList to [ "Lara", "Amelia", "John" ]>>\
    Name List: <<print $nameList.join(", ")>>
    <<NPC>><<set $first to $NPC>>\
    Character 1 is named $first.name. $first.name has $first.eyes eyes and $first.hair hair.
    <<NPC>><<set $second to $NPC>>\
    Character 2 is named $second.name. $second.name has $second.eyes eyes and $second.hair hair.
    <<NPC>><<set $third to $NPC>>\
    Character 3 is named $third.name. $third.name has $third.eyes eyes and $third.hair hair.
    <<NPC "Fred">><<set $fourth to $NPC>>\
    Character 4 is named $fourth.name. $fourth.name has $fourth.eyes eyes and $fourth.hair hair.
    <<NPC "Barney">><<set $barney to $NPC>>\
    Character "Barney" is named $barney.name. $barney.name has $barney.eyes eyes and $barney.hair hair.
    
    Basically, you'd gain some flexibility by being able to generate NPCs with specific names, but randomized details. Not a huge improvement, but it might be nice to have.
  • I think it's a great improvement actually, mainly because of the flexibility in there to be more forward-thinking.

    Thanks.
  • name : $args[0] || $nameList.pluck(),

    I didn't realize that SugarCube accepted the double-pipe. I was trying to help someone on the forum with "or" earlier and just didn't know. Thanks.
Sign In or Register to comment.