AS3 Null Object Reference After Null Check - actionscript-3

I have been looking for a solution to this for a while now but cannot find anything.
This problem is very odd so I'll give some background. I am making a tower defense game, and while implementing status effects on enemies (slowed, on fire, etc.) I ran into a weird problem. When the tower kills the enemy with a projectile everything is fine, but for some reason when the enemy is killed by a status effect, I get a null object reference error (both are handled with the method "Enemy.damage(damage:int)"). The weird thing about this is that the status effects are stored in an array that is only referenced in 3 spots. In the ENTER_FRAME event (one ENTER_FRAME event is used to process every aspect in the Game), in the constructor (status = new Array();) and in the destroy method.
One weird part is that if the "status" variable was null, other variables from the destroy method would have given me errors a long time ago. The really weird part is that I have a null check right before I get the error.
Here is where I get the error:
//STATUS EFFECTS
if (status == null) {
trace("test");
}
if (status != null && status.length != 0) {
for (var i:int = 0; i < status.length; i++) {//LINE 101
status[i].applyEffect();
}
}
Here is the error:
TypeError: Error #1009: Cannot access a property or method of a null object reference.
at game.enemies::Enemy/update()[C:\flash\game\enemies\Enemy.as:101]
"test" never gets printed, and even with the null check I still get the error
EDIT: Here is the Enemy class: http://pastebin.com/LAyMMB1P
The StatusEffect & FireEffect classes: http://pastebin.com/GTGDmjt8 (I can only post 2 links, each is in its own file)
status variable is referenced in StatusEffect.destroy() and here:
override public function onHit(e:Enemy) {
if (upgradeLevel >= 1) {
var onFire = false;
for (var i:int = 0; i < e.status.length; i++) {
if (e.status[i].name_ == "fire") {
onFire = true;
}
}
if (!onFire) {
e.status.push(new FireEffect(e));
}
}
if (upgradeLevel >= 2) {
if (enemiesHit.indexOf(e) == -1) {
enemiesHit.push(e);
e.damage(1);
}
if (upgradeLevel == 2 && enemiesHit.length == 2) {
destroy();
}
} else {
e.damage(super.damage);
destroy();
return;
}
}

This is happening because you can potentially destroy your enemy in the middle of the for loop on line 101.
Destroying the enemy sets the status array to null, so on the next iteration when it checks if i < status.length, status is null, thus the error.
So to illustrate, stepping through the code, let's assume at line 101 there are two items in the status array:
First loop, item at index 0 gets applyEffect() called on it.
inside applyEffect() the specificApplication() method can be called.
inside specificApplication() the super.enemy.damage(1); method is called.
inside the enemy.damage() method, you can potentially call the enemy.destroy method
inside the enemy.destroy method, you set status = null.
NOW the loop from line 101 loops, but status is now null.
Fix this by breaking out of your loop if the enemy is destroyed.
for (var i:int = 0; i < status.length; i++) {//LINE 101
status[i].applyEffect();
if(!status) break;
}
You also have some issue with splicing:
enemy.status.splice(enemy.status.indexOf(this), 1);
While there is nothing wrong with that line on it's own, let's step through the code:
line 101 of Enemy class:
for (var i:int = 0; i < status.length; i++) {
You loop through the status array. For this example, let's say you have two items in the array.
Item A is at position 0. Item B is at position 1 in the array.
First loop, i has a value of 0.
You call status[i].applyEffect(), since i == 0, that is item A.
Inside applyEffect let's say your condition is met so you call destroy (on the StatusEffect class)
Inside the StatusEffect.destroy() method you splice the status array.
Now status has only one item, item B, and item B now has an index of 0 (instead of 1).
Your loop loops, now i == 1.
However, there is no longer any item at index 1 so the loop exits, skipping item B.
To solve it, iterate backwards instead, then your indices won't get out of whack when you splice:
var i:int = status.length
while(i--){
status[i].applyEffect();
}
As an aside, it would be much cleaner if you used var status:Vector.<StatusEffect> instead of an var status:Array. You'd get compile time checking and type safety that way.

Related

How to push textbox value into Array and recall it

This is the first time I ask in this website, if there's any mistake and inappropriate thing apologize in advance
I was trying to make my own basketball score board using Action Script 3 but I am stuck on the way of calling a player fouls score and show it individually.
In the picture, the second box is where to type a player number who made a foul and the third box is where the number shows how many times this player has fouled.
I need to know how to code an array store that receives a value from the 'Player' textbox as the player number and stores the fouls count with the specific player's number too (if I type another player number it will count a foul separately and next time I type the exist number it will call out how many times he fouls)
You could use an array, or a dictionary, or even dynamic properties.
Let's assume your text fields are called txtTeam1fouls, txtPlayer, txtFouls, txtTeam2fouls. Let's also say you have a var called curTeam that stores an integer identifier for the team whose player number you enter (for this example, either 1, or 2).
Here is an example of storing a basic object in an Array:
var fouls:Array = []; //create a new empty array
//add a listener for when you type something into the player text input
txtPlayer.addEventListener(KeyboardEvent.KEY_UP, updatePlayer);
//this function retries a foul record from the array for a specific player
function getFouls(player:int, teamId:int):Object {
//loop through the array until you find a match
for(var i:int=0;i<fouls.length;i++){
if(fouls[i].player === player && fouls[i].team === teamId){
return fouls[i];
}
}
//if no record in the array, return 0
return null;
}
//this function updates the foul text field when you change the what's in the player text field
function updatePlayer(e:Event):void {
var foulRecord = getFouls(int(txtPlayer.text), curTeam);
//if a foul record exists, use it's foul count, if not use 0
txtFouls.text = foulRecord ? foulRecord.fouls.toString() : 0;
}
//call this function whenever you add a new foul record.
function addFoul(player:int, teamId:int):void {
//first, see if there is an existing foul record in the array
var foulObj:Object = getFouls(player, teamId);
if(!foulObj){
//if there was no record, create one, then push (add) it to the array
foulObj = {team: teamId, player: player, fouls: 1};
fouls.push(foulObj);
}else{
//if there is an existing record, increment it.
foulObj.fouls++;
}
//now update the totals for each team
var team1Ctr:int = 0;
var team2Ctr:int = 0;
for(var i:int=0;i<fouls.length;i++){
switch(fouls[i].team){
case 1:
team1Ctr++;
break;
case 2:
team2Ctr++;
break;
}
}
txtTeam1Fouls.text = team1Ctr.toString();
txtTeam2Fouls.text = team2Ctr.toString();
}

Save Number with Shared Object and Add to that Saved Number.

Hey everyone so basically what I am trying to accomplish is saving a number with the Shared Object which the the coins that the player collects in the game and if they player quits out of the game and comes back to play again the amount of coins he had when he quit will still show and he will be able to add to that amount if the player picks up more coins. I had this working for a bit but then the textfield started displaying "NAN".
Here is how I have it set up.
The Variable private var nCoins:Number;
In the constructor function I have:
sharedObjectCoins = SharedObject.getLocal("CoinsData");
nCoins = 0 + sharedObjectCoins.data.tCoins;
if (sharedObjectCoins.data.tCoins == null)
{
sharedObjectCoins.data.tCoins = nCoins;
}else
{
trace("Save data found."); // if we did find data...
loadDataTimeAttack(); // ...load the data
}
and in the games Enter.Frame Loop I have the function saveDataCoins which is setup like so:
private function saveDataCoins():void
{
if (nCoins > sharedObjectCoins.data.tCoins )
{
sharedObjectCoins.data.tCoins = nCoins;
}
coinsGraphic.coinsText.text = " " + sharedObjectCoins.data.tCoins;
sharedObjectCoins.flush();
}
not sure if you need the function to where the hitTest takes place between the coins and player but here it is:
private function checkPlayerHitCoins():void
{
for (var i:int = 0; i < aCoinsArray.length; i++)
{
//get current point in i loop
var currentCoins:mcCoin = aCoinsArray[i];
//test if player is hitting current point
if(player.hitTestObject(currentCoins))
{
nCoins += 1;
updatecoinsTextScore();
updateCoinsPauseScreen();
//Add points sound effects
var coinsSEffect:Sound = new coinsSound();
coinsSEffect.play();
//remove point on stage
currentCoins.destroyCoins();
//remove points from array
aCoinsArray.splice(i, 1);
trace("Hit: " + aCoinsArray.length);
}
}
}
Please if anyone could help me with this maybe point something out that I am doing wrong. This code worked perfect one time and when I closed the screen and came back to re test it the textfield displayed NAN and thats it when I hitTest the coins sometimes the NAN switches to a number for like a second but then goes back to NAN.
The first time (or rather every time it creates a new shared object) you will be trying to add undefined to 0, which will result in either a runtime error or NaN.
You need to check if the value exists before attempting to do addition with it.
if(sharedObjectCoints.data && sharedObjectCoins.data.tCoins && !isNaN(sharedObjectCoins.data.tCoins)){
nCoins = Number(sharedObjectCoins.data.tCoins); //there's not point in adding 0
trace("Save data found."); // if we did find data...
loadDataTimeAttack(); // ...load the data
}else{
sharedObjectCoins.data.tCoins = nCoins;
}
Also, if you don't manually set a value to a number var, it will start off life as NaN. eg var nCoins:Number will be NaN until you set it to something.
That said, working with the sharedObject directly like this is a very sloppy way to code your program. Really you should just use shared object to load and save the value, and everything in between use a strongly typed variable.
var nCoins:int = 0;
var tCoins:int = 0;
sharedObjectCoins = SharedObject.getLocal("CoinsData");
if(sharedObjectCoins.data && sharedObjectCoins.data.tCoins && !isNaN(sharedObjectCoins.data.tCoins){
tCoins = int(sharedObjectCoins.data.tCoins);
}else{
//no shared object, use default value for tCoins
tCoins = 0; //or whatever it should start off as.
}
Then write a save function
private function saveSharedObject():void {
sharedObjectCoins.data.tCoins = tCoins;
sharedObjectCoins.flush();
}
Then replace all other instances of sharedObjectCoins.data.tCoins with the var tCoins
It's probably best not to flush the shared object every frame for performance purposes.
Also, shared objects may or may not actually save, depending on user preferences, storage space available, etc. They should not be relied upon for critical data retention.
You can listen for problems with the shared object with AsyncErrorEvent.ASYNC_ERROR I believe (It's been a while since I've worked with AS3 Shared Objects)

removed object not disappearing and 2025 error, as3

I'm trying to make a turn based strategy game on a grid map. To move your characcters around, I'm using click detection to see which direction the player intends. The code I'm using removes the old object, I presume, the adds the new one in one space to the right. The problem is that when I remove my object the image of the object remains where it began, while the actual objects reappears in the correct position. Then, when I try again, flash throws a 2025 error. Here is the code.
if(s == 0){
sarray[s] = [main, maincharacter, mc, 3]
enemyiden = "gwr"
}
else if(s == 1){
sarray[s] = [mcaxe, mainaxe, mcaxeman, 3]
enemyiden = "gar"
}
else if(s == 2){
sarray[s] = [mchorse, maincav, mccavalry, 5]
enemyiden = "gcr"
}
for(var g:int = 0; g < tilepositions.xcoords.length; g++){
if(tilepositions.xcoords[g] == Allies[s][0] - 12.5){
break
}
}
for(var h:int = 0; h < tilepositions.ycoords.length; h++){
if(tilepositions.ycoords[h] == Allies[s][1] - 12.5){
break
}
}
if(mouseX < Allies[s][0] && Math.abs(mouseX - Allies[s][0]) < 37.5 && Math.abs(mouseY - Allies[s][1]) < 12.5 ){
trace("test1")
this.removeChild(sarray[s][0])
sarray[s][0] = new Sprite
this.addChild(sarray[s][0])
trace(this.contains(sarray[s][0]))
sarray[s][1] = new sarray[s][2]
sarray[s][0].addChild(sarray[s][1])
sarray[s][0].x = tilepositions.xcoords[g-1]
sarray[s][0].y = tilepositions.ycoords[h]
Allies[s][0] = sarray[s][0].x + 12.5
movecurr[s] += 1
break
}
"s" is a for loop variable that counts upward. Any help would be much appreciated.
The problem is within this line: this.removeChild(sarray[s][0]). And the error says it all: ArgumentError: Error #2025: The supplied DisplayObject must be a child of the caller, which means that sarray[s][0] is not child of this. There's no magical way to tell you why, as we don't know, but if you fix this the problem will be resolved :)

Using two eventlisteners for one function in Actionscript

I'm making a dots and boxes game in flash using actionscript 3.
Currently I'm completely stuck as I want the player to click two buttons before a line is displayed. I planned to do something like this using the if statement
if button1 and button2 clicked
line1 visible = true
I've also tried adding eventlisteners with one function
function showLine(e:Event):void {
blue0001.visible = true
}
dot00.addEventListener(MouseEvent.CLICK, showLine);
But as far as I know this can only be used when you want to click one button. Is there anyway to have two eventlisteners satisfyed before the function is carried out?
Also how would I (if i can) use the if statements to carry out this?
You would probably do something like this, pseudo-code:
Assume all of the dots are in a dots array.
for (var i: Number = 0; i < dots.length; i++) {
dots.addEventListener(MouseEvent.CLICK, dotClicked, false, 0, true);
}
dotSelected = null;
function dotClicked(evt:MouseEvent):void {
if (dotSelected && isNeighbor(evt.target, dotSelected)) {
showLineConnecting(evt.target, dotSelected)
dotSelected = null;
} else if (!dotSelected) {
highlightDot(evt.target);
dotSelected = evt.target;
} else {
showError("You must click an adjacent dot");
}
}
At the request of the OP, this is what's going on.
for (var i: Number = 0; i < dots.length; i++) {
dots.addEventListener(MouseEvent.CLICK, dotClicked, false, 0, true);
}
Add an event listener to every dot. Here I am assuming you already have an array of dots defined. Each instance in the Array would be a MovieClip (likely), Sprite, or another DisplayObject.
dotSelected = null;
We will use a variable to keep track of any currently selected dot. Since no dot will be selected when the game starts, we set it to null.
function dotClicked(evt:MouseEvent):void {
if (dotSelected && isNeighbor(evt.target, dotSelected)) {
showLineConnecting(evt.target, dotSelected)
dotSelected = null;
} else if (!dotSelected) {
highlightDot(evt.target);
dotSelected = evt.target;
} else {
showError("You must click an adjacent dot");
}
}
This is the function that will get called when any dot is clicked. For explanation's sake, let's take the first click of the game. dotSelected is null, so the first if is false. The second if though is true, because (!dotSelected) is true. So, we run some function I called highlightDot with the dot as the argument. That function could look something like this:
function hightlightDot(dot:Dot):void {
dot.gotoAndStop("selected");
}
Now a second click is made. Now the first part of the first if, dotSelected, is true. The second part is now evaluated. Again I put in a made up function isNeighbor. The isNeighbor function takes two arguments, the dot that was just clicked and the dot that has already been clicked. This function needs to make sure the two dots are adjacent. This could be something like...
function isNeighbor(dot1:Dot, dot2:Dot):void {
return ((dot1.xGrid == dot2.xGrid && Math.abs(dot1.yGrid - dot2.yGrid) == 1) || (Math.abs(dot1.xGrid - dot2.xGrid) == 1) && dot1.yGrid == dot2.yGrid));
}
The above function assumes that the instances of Dot have some properties xGrid and yGrid which defines where in the playing board they sit. If they are in the same row and 1 column apart they are neighbors. If they are in the same column and 1 row apart they are neighbors.
The last thing that happens in a function showLineConnecting is called. That function will again take the two adjacent dots as arguments. It will then draw a line between them in whatever way you choose to do that. Finally, dotSelected is set back to null, allowing another set of dots to be selected.
One thing I just realized, it would probably be helpful to have an additional property that gets triggered on a Dot when it is connected to all of its neighbors so it could no longer be selected.
You will also need logic to handle knowing that a box has been created. For that you would likely just iterate the possibilities given the line that was just drawn. For each line drawn there are only two possible boxes that have been created. So check them both. Watch out for edges.

Remove multiple rows in a single pass in JTable AbstractDataModel

i've got an issue with Jtable and my dataModel.
My table model extends AbstracttableModel, the datas are stored in a Vector.
I'have a function witch is suppose to remove one or more row.
These rows are not necessarily contiguous because my jtable set a selectionMode as this:
jTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
The function to remove row (one by one):
public void removeMessageRow(Integer p_idMessage) {
Enumeration<MlMessage> alldatas = vData.elements();
while (alldatas.hasMoreElements()) {
MlMessage m = alldatas.nextElement();
if (m.getIdMessage() == p_idMessage) {
int idx = vData.indexOf(m);
boolean result = vData.remove(m);
// fireTableDataChanged();
// fireTableRowsDeleted(idx, idx);
fireTableStructureChanged();
return;
}
}
When i launch the function, i execute the loop without problem, in step-by-step mode, i can see the vData object updated and if i execute this only one time there is no probleme with the GUI. The problem appear when i select more than one row.
For example, i selected row number 0 and number 1 in my table and i lauch the function removeMessageRow, at the first execution, the vDataObject is correctly updated (all the datas are shiffted and the last elements of this vector is set to null by the call to vData.remove(m).
So in my case, i expect that at the second execution, the object to find should be at position 0 but it's at position 1 as the vData Object as never been updated.
Does anybody have an idea about that?
I've tried many on fire... stuff but no one semms to execute immediatly.
Thanks for any help by advance and sorry for my Shakespeare's language.
Add a method in your model taking a collection of indices to remove (Set<Integer>, or int[]), sort this collection, iterate backwards in this collection, and remove the elements in the list of objects of your model:
public void removeRows(int[] indices) {
Arrays.sort(indices);
for (int i = indices.length - 1; i >= 0; i--) {
this.data.remove(indices[i]);
fireTableRowsDeleted(indices[i], indices[i]);
}
}
int[] selectedRow = tblUsuarios.getSelectedRows();
int del=1;
for(int j=0; j<selectedRow.length; j++){
modTabla.removeRow(selectedRow[j]);
if(j<selectedRow.length-1){
selectedRow[j+1] = selectedRow[j+1]-del;
del = del+1;
}
}
//modTabla = defaultTableModel
//tblUsuarios = jtable