Destroying scopes/isolated scopes? Angularjs - javascript

This code is a little wild but I am more concerned about concepts here than definite code responses. I am working on a combat system for a game. I have npc characters running on an "attack" loop. Once the player has reduced an npc bad guy's hit points to zero, I want to remove that entity from the map/DOM/scope etc. ...some code (the code in question will be towards the end) -
Loading the npc character data from database..an array, all same properties, but different values. $scope.encounters gets ng-repeated in the HTML and builds out the characters on the screen. That data is then send to $scope.encounter_a() function to do more stuff.
db.getNpcCombat(npc_combat_set).success(function(data){
$scope.encounters = [];
$scope.encounters = data;
angular.forEach(data, function(value, key){
console.log(value);
$scope.encounter_a(value);
});
This sets a timer for a progress bar with different rates for different npcs, once the time hits 100, cancels timer and then runs the attack function...which was looped through, so it is running with different times for each npc according to their data. So value is the data from one npc entity.
$scope.encounter_a = function(value){
value.attack_count = 0;
$timeout(function(){
var set_timer = $interval(function() {
value.attack_count += 1;
if(value.attack_count >= 100){
$interval.cancel(set_timer);
$scope.attack(value);
}
}, value.attack_rate);
}, 500);
}
This part looks a little crazy but does it's job. This is still receiving the data of one npc entity. Each enemy has different attacks, so basically, this is getting that certain NPC's attacks data from the database, then randomizing which attack will get used, it then calculates whether or not the attack will hit the player character. Whether or not the attack hits or misses, it will loop back to encounter_a().
$scope.attack = function(data){
$timeout(function(){
db.getNpcFeats(data.feats).success(function(feat_data){
//randomize which feat is used ...get amount of feats
var count = feat_data.length;
//randomize number
count = getRandomInt(1, count);
count = count - 1;
//get feat_hit success - gets random number between 1-10
feat_success = getRandomInt(1, 10);
$('#enemy_'+data.id+' ul li .attack_text h5').html(feat_data[count].feat_name + '!');
$('.attack_text').removeClass('fadeOut').addClass('fadeIn');
$timeout(function(){
$('.attack_text').removeClass('fadeIn').addClass('fadeOut');
}, 1000);
if(feat_data[count].feat_chance >= feat_success){
$scope.damage_amount= getRandomInt(parseInt(feat_data[count].feat_min_damage), parseInt(feat_data[count].feat_max_damage));
$scope.$watch('damage_amount', function() {
$('.feat_name').html(feat_data[count].feat_name);
$('.feat_damage').html('Damage - ' +$scope.damage_amount);
$('.feat_hit .text-danger').html("You've been hit!!");
$('.outer-container').addClass('tada');
$('.feat_hit').removeClass('fadeOutDown').addClass('fadeInUp');
$scope.player_hp = $scope.player_hp - $scope.damage_amount;
if($scope.player_hp >= 0){
//future player death function
}
$timeout(function(){
$('.feat_hit').removeClass('fadeInUp').addClass('fadeOutDown');
}, 2000);
});
}
});
$scope.encounter_a(data);
}, data.attack_delay);
}
Finally, here is the code that is giving me trouble. This code is essentially disconnected from the above code. This is the attack code for the player attacking the NPCs. Which has worked, except for when the NPC's hitpoints reach zero, not sure what to do.
$scope.playerFeatAttack = function(player_feat){
//if player has not selected npc to attack, show message and prevent attack then remove message else allow attack
if (!$('.enemy_container').hasClass('attack_selected')){
$('.helper_dialog_area').removeClass('lightSpeedOut');
$('.helper_dialog_area').removeClass('hide');
$('.helper_dialog_area h4').html('CLICK AN ENEMY TO ATTACK!!!');
$('.helper_dialog_area').addClass('lightSpeedIn');
$timeout(function() {
$('.helper_dialog_area').removeClass('lightSpeedIn');
$('.helper_dialog_area').addClass('lightSpeedOut');
}, 2500);
}else{
//calculate hit damage
var hit_damage = getRandomInt(parseInt(player_feat.feat_min_damage), parseInt(player_feat.feat_max_damage));
//calculate attack success
var attack_success_roll = getRandomInt(1, 10);
//if attack passes, run attack
if(player_feat.feat_chance >= attack_success_roll){
//determine which NPC to attack based on the .attack_selected class
//not sure if this is a good way but this is how getting the specific NPC hitpoints
var elem = angular.element($(".attack_selected .npc_hp")).scope();
$scope.npc_hp = $('.attack_selected .npc_hp').html();
//watch for changes in the hitpoints
$scope.$watch('npc_hp', function(){
//change the hitpoints on the screen using jquery..probably not ideal way
var npc_hp = parseInt($scope.npc_hp) - parseInt(hit_damage);
$('.attack_selected .npc_hp').html(npc_hp);
var hp_after_attack = $('.attack_selected .npc_hp').html();
This main code in question -
if(hp_after_attack <= 0){
$('.attack_selected').addClass('flipOutX');
var what = angular.element($('.attack_selected')).scope();
}
});
}
}
}
This last part is the code in question. If the NPC's hitpoints go below 0, first planning to add a CSS class to do an animation, then want to remove that character from the game. If I console log this -
var what = angular.element($('.attack_selected')).scope();
Which is the element I want to remove, I can see the encounter object in the main scope which is that NPC entities data. I tried to change that to NULL, figuring it would eliminate the entity's data, hence not being able to do anything. Also just tried removing the element from the DOM, which of course, removes it from the screen, but then it keeps running attacks.
In $parent of the scope, I can see the $scope.attack() function..I changed that to NULL, but then that stops all of the NPCs from attack.
What I need to do is kill the attack() function for the specific NPC...but when I console $scope, it looks to me like there is only 1 attack function in the scope, not seperate ones for each "encounter" entity/npc.
Is there some way I can code the attack() function to be attack to the main scope of each of these elements? Maybe an isolated scope (which I don't really understand what it is)?
This is my learning AngularJS project...so any advice on a better way to accomplish the above and also to eliminate the NPC from the battle after it's be killed would be really great. Let me know if you need any more info from me.

Related

Multiple of document.getElementById("elementId").innerHTML = () causing second one to return as null

First, let me explain what I'm trying to do: I want to make a script for a video game that counts how much money is in the game, and create an element to display it. The tracking the money part was easy, but apparently making elements is like the most confusing thing i've tried to do yet.
Lightshot screenshot of chrome console: https://prnt.sc/shszc2
The blue-highlighted line in the screenshot gave an error after being executed twice. I boxed the error message in red.
I'll take some code out of the script I have, mainly aiming for code that is important for the issue i want help with, leaving out code that I understand.
Also note that I am extremely new to generating graphics in Javascript, so if my ways of making elements are horrendous, then it's because I just kept trying random crap until something seemed to work and stuck with whatever that was.
// The elements that I created. Again i know next to nothing about elements, so the only thing that I
// know will work is this catastrophe.
var initialDiv = document.getElementById('onecup');
mainText = initialDiv.appendChild(document.createElement('mainText'));
mainText.style.position = 'absolute';
mainText.style.left="50%";
mainText.style.top="64px"
mainText.style.width = "290px";
mainText.style.height = "160px";
mainText.style.color = "white";
mainText.style.zindex = 1;
mainText.style.fontSize = "18px"
trackerBack = mainText.appendChild(document.createElement('trackerBack'));
trackerBack.style.position = 'absolute';
trackerBack.style.left="-200px"
trackerBack.style.top="0px"
trackerBack.style.width = "400px";
trackerBack.style.height = "160px";
trackerBack.style.backgroundColor = "black";
trackerBack.style.opacity = ".20"
trackerBack.style.zindex=1;
diffTotal = trackerBack.appendChild(document.createElement('diffTotal'));
diffTotal.id = "diffTotal"
diffTotal.style.position = 'absolute';
diffTotal.style.top="20%"
diffTotal.style.left="40%";
diffTotal.style.color = "rgba(255,255,255,255)";
diffTotal.style.opacity = "1"
diffTotal.style.zindex = 2;
diffTotal.style.fontSize = "30px"
diffFielded = diffTotal.appendChild(document.createElement('diffFielded'));
diffFielded.id = "diffFielded"
diffFielded.style.position = 'absolute';
diffFielded.style.top="-15px"
diffFielded.style.left="0px";
diffFielded.style.color = "rgba(255,255,255,255)";
diffFielded.style.opacity = "1"
diffFielded.style.zindex = 2;
diffFielded.style.fontSize = "20px"
// This function is used by a latter function to set the values of the text elements. I initially didn't
// have this but thought adding it would help, but nothing changed.
// By the way, "toBna2" stands for to "big number abbreviation". It doesn't do anything major, besides
// shrink down numbers. Tried removing it, problem still persists.
conductValues = function(targetName, targetAssignment) {
document.getElementById(targetName).innerHTML = toBna2(targetAssignment)
}
// This looping function controls the values that the elements display. However, I removed the code that
// tells the function what values to make the elements, so if you want to test it, I guess just define
// the 4 values as anything or make your own.
findValueDiff = setInterval(function() {
// If i make one of these lines a comment, it works, regardless of which one it is. But if i let both of
// them run, the second document.getElementById("elementId") returns as null. Always the second one.
conductValues("diffTotal", (aValP + aValU - bValP - bValU))
conductValues("diffFielded", (aValU - bValU))
}
I even tried doing this:
conductValues = function(targetName, targetAssignment) {
if (document.getElementById(targetName) != "undefined") {
document.getElementById(targetName).innerHTML = toBna2(targetAssignment)
}
}
But all that does is make the function fail on the first attempt, because always the second document.getElementById("elementId") returns as null.
I'm not entirely sure if I included enough information, but I don't know what else to add so hopefully I did. But if you need more information, just ask and i'll try to edit this post as swiftly as possible.
Thanks to all responders, and I hope you stay healthy as you have fun coding.
Edit 1: Thought i would get something different if I set the entity's variables one at a time like this:
conductValues = function(targetName, targetAssignment) {
document.getElementById(targetName).innerHTML = toBna2(targetAssignment)
}
loopMode=0
findValueDiff = setInterval(function() {
if (loopMode == 0) {
conductValues("diffTotal", (aValP + aValU - bValP - bValU))
loopMode = 1
} else {
conductValues("diffFielded", (aValU - bValU))
loopMode = 0
}
}, 1000
);
But the problem still hasn't changed. Second time it tries to update, it fails.
Ah, I got it:
diffTotal = trackerBack.appendChild(document.createElement('diffTotal'));
diffFielded = diffTotal.appendChild(document.createElement('diffFielded'));
conductValues("diffTotal", (aValP + aValU - bValP - bValU))
conductValues("diffFielded", (aValU - bValU))
conductValues = function(targetName, targetAssignment) {
document.getElementById(targetName).innerHTML = toBna2(targetAssignment)
}
Those are the lines, which cause the error.
diffFielded is a child of diffTotal. In the first conductValues call, you replace the innerHTML of diffTotal. When you are doing this, you are removing diffFielded, because it's replaces by the new value and then it cannot by found anymore because it does not exist anymore.
I assume diffFielded should actually be another child of trackerBack, so you should do:
diffFielded = trackerBack.appendChild(document.createElement('diffFielded'));
Tip:
Move the style stuff into a css file.

Creating a for loop that loops over and over =

So I have a weird problem (as I can do this using dummy code, but cannot make it work in my actual code) -
The concept is simple - I need a for loop that upon hitting its max "I" number reverts "I" to 0 again and creates a loop over and over -
DUMMY CODE:
for(i=0;i<10;i++){
console.log(i);
if(i === 10){
i = 0
}
}
Now for the longer code (sorry)
function reviewF(){
// add ID to each of the objects
reviews.forEach((e, i)=>{
e.id = i
})
// get the elements to be populated on page
var name = document.querySelector('p.name');
var date = document.querySelector('p.date');
var rating = document.querySelector('.rating_stars');
var review = document.querySelector('p.review_content_text');
// reverse the array - so the newest reviews are shown first (this is due to how the reviews where downloaded)
var reviewBack = reviews.slice(0).reverse();
// start the loop - go over each array - take its details and apply it to the elements
/**
* THIS IS WHAT I WOULD LIKE TO LOOP OVER FOREVER
*
* **/
for (let i = 0; i < reviewBack.length; i++) {
(function(index) {
setTimeout(function() {
// document.getElementById('reviews').classList.remove('slideOut')
name.classList.remove('slideOut')
date.classList.remove('slideOut')
rating.classList.remove('slideOut')
review.classList.remove('slideOut')
name.classList.add('slideIn')
date.classList.add('slideIn')
rating.classList.add('slideIn')
review.classList.add('slideIn')
name.innerHTML = reviewBack[i].aditional_info_name;
date.innerHTML = reviewBack[i].Date;
rating.innerHTML = '';
review.innerHTML = reviewBack[i].aditional_info_short_testimonial;
if(reviewBack[i].aditional_info_short_testimonial === 'none'){
reviewBack.innerHTML='';
}
var numberOfStars = reviewBack[i].aditional_info_rating;
for(i=0;i<numberOfStars;i++){
var star = document.createElement('p');
star.className="stars";
rating.appendChild(star);
}
setTimeout(function(){
// document.getElementById('reviews').classList.add('slideOut')
name.classList.add('slideOut')
date.classList.add('slideOut')
rating.classList.add('slideOut')
review.classList.add('slideOut')
},9600)
}, i * 10000)
})(i);
// should create a infinite loop
}
console.log('Loop A')
}
// both functions are running as they should but the time out function for the delay of the transition is not?
reviewF();
EDITS >>>>>>>>
Ok so I have found a hack and slash way to fix the issue - but its not dry code and not good code but it works.....
this might make the desiered effect easier to understand
reviewF(); // <<< this is the init function
// this init2 function for the reviews waits until the reviews have run then
// calls it again
setTimeout(function(){
reviewF();
}, reviews.length*1000)
// this version of the innit doubles the number of reviews and calls it after that amount of time
setTimeout(function(){
reviewF();
}, (reviews.length*2)*1000)
From trying a bunch of different methods to solve this issue something I noticed was when I placed a console.log('Finished') at the end of the function and called it twice in a row (trying to stack the functions running..... yes I know a horrid and blunt way to try and solve the issue but I had gotten to that point) - it called by console.log's while the function was still running (i.e. the set time out section had not finished) - could this have something to do with it.
My apologies for the rough code.
Any help here would be really great as my own attempts to solve this have fallen short and I believe I might have missed something in how the code runs?
Warm regards,
W
Why not simply nest this for loop inside a do/while?
var looping = True
do {
for(i=0;i<10;i++){
console.log(i);
}
if (someEndCondition) {
looping = False;
}
}
while (looping);
I would think that resetting your loop would be as simple as setting "i = 0" like in the dummy code. So try putting the following into your code at the end of the for loop:
if(i === 10){
i = 0;
}

Understanding Private Variables (Closure?) in Javascript with specific example

OK, I have tried to use a closure to no avail on the following code to keep a variable private. I am brand new to javascript and have read a number of posts about closures and can still not wrap my head around them. Below, I have a function that, upon each press of a particular button, displays the next word in an array. I want my counter variable ("whatNumber" below) that I am using in this function to not be global but I cannot figure out how. Here is my simple code:
var wordList = ["jumper", "stumpy", "smelly gumdrops", "awesome puttputt", "soilent green"];
var whatNumber = 0;
function changeWord(){
if (whatNumber < wordList.length) {
alert(wordList[whatNumber]);
whatNumber++;
}
};
function changeWord(){
var wordList = ["jumper", "stumpy", "smelly gumdrops", "awesome puttputt", "soilent green"];
var whatNumber = 0;
return function alertWord(){
if (whatNumber < wordList.length) {
alert(wordList[whatNumber]);
whatNumber++;
}
}
};
//to run this
var alertNewWord = changeWord();
alertNewWord() //jumper
alertNewWord() //stumpy
This comes with a bonus of being able to have different functions having different levels of alerting. e.g: if you do another var anotherAlertFn = changeWord() and you call anotherAlertFn() it will result in "jumper". The initial functions (i.e: alertNewWord()) will still have it's own state, i.e: whatNumber === 3 while anotherAlertFn has whatNumber === 1. This can be very useful, imagine a function keeping score for different players in a game. Every player can use the same function without being able to cheat (i.e: change their score) and never affecting other players' scores.

setInterval() speeds up when using multiple from object method

I have been having problems with my setIntervals(). I know these issues appear a lot but I can't seem to work out what the exact problem with my implementation is. Every time I instantiate a new Obstacle() it clears the set interval used to rotate the instance of the obstacle, and the next instantiation of the obstacle seem to rotate twice as fast! I'm sure it's to do with scope but I'm a relative beginner so I'm not quite sure what's going on here. Any more info can be provided.
var obstacleCount = 1;
function Obstacle(){
this.angle = 0;
this.id = obstacleCount;
this.elPrefix = "cookie-";
this.el = '.' + this.elPrefix + this.id;
$('#game-wrapper').append('<div class="' + this.elPrefix + this.id + '"></div>');
obstacleCount += 1;
}
var intervals = new Array();
Obstacle.prototype.roll = function() {
self = this;
intervals[self.id] = setInterval(function(){
self.angle -= 3;
$(self.el).rotate(self.angle);
}, 5);
$(self.el).animate({
right: 1000
}, 4000, 'linear', function(){
$(self.el).remove();
clearInterval(intervals[self.id]);
});
};
var obstacles = new Array();
setInterval(function(){
obstacleID = obstacleCount;
obstacles[obstacleID] = new Obstacle();
obstacles[obstacleID].roll();
}, 1000);
In most games there is a single update loop that handles all of the update logic for your game. I would recommend using a single interval in which all objects are updated instead of giving each object it's own scheduled update via setInterval. You gain a few advantages from the update loop method:
Don't have to keep track of intervals.
Since since setInterval is not consistant with timing (it fires when the main script is finished executing and has some extra time before the next go around, but only if it's interval time is up. This means you cannot completely rely on it's timing being what you asked it to be.) you are better off having all your objects updated at the time so you are as consistant as you can be.
General pseudo code to get you started:
initialize objects
add all objects to an array
setInterval(updateObjects, 30);
updateObjects(){
for each object in array
object.roll();
}

JavaScript - Timer Initiation

I'm trying to make my enemy fire every second. Right now - it's every frame.
Within my enemy object, I have a piece of code that is initiated when the player is within range:
this.shotBullet = false;
var object = this;
object.Fire();
This is the enemy fire function:
this.Fire = function(){
console.debug("Firing | Shot: " + this.shotBullet);
if(!this.shotBullet){
if(this.weapon == "pistol")
PistolEnemy(this);
this.shotBullet = true;
}
};
And my PistolEnemy function:
PistolEnemy = function(operator){
var user = operator;
console.debug("user:" + user.tag);
var bulletDamage = 1;
var bulletSpeed = 20;
var b = new Rectangle( user.x + (user.width / 2) - 4, user.y + (user.height / 2) - 4, 8, 8);
var velocityInstance = new Vector2(0, 0);
velocityInstance.x = Math.cos(user.rotation) * bulletSpeed;
velocityInstance.y = Math.sin(user.rotation) * bulletSpeed;
var bulletInstance = new Bullet(velocityInstance, b, "Enemy", bulletDamage, "blue");
/*audioPistol.volume = 0.5;
audioPistol.currentTime = 0;
audioPistol.play();*/
user.bullets.push(bulletInstance);
user.shotBullet = true;
};
I've tried playing around with the 'setInterval', but it doesn't work well. Most of the times, it waits for a second, then sprays a load of bullets.
All I want it for a enemy bullet to initiate every second.
Thanks
var triggerID = window.setInterval(function(){firing_clock()},1000);
function firing_clock()
{
// this will execute once per second....sort of
}
Mozilla window.setInteral doc
So, one thing you should known is if your browser gets busy it will get 'late'. Mozilla used to have an extra non-standard parameter detailing "actual lateness", but it no longer does - but the point was that you are asking the browser to try to do something once per second, but if it gets busy it will get behind or skip a few rounds (how the browser handles it differs by browser).
Ideally what you would do here is register your enemy object with a list that firing_clock() would work through to dispatch firing commands to all live enemies. This cuts overhead by only using one global timer, rather than one timer per object on screen.
Try this with just one hard-coded enemy and see how it works. If it still doesn't work, then it's a bigger problem as there is no javascript "guaranteed accurate timer" that I'm aware of.
But it should work, so long as things don't get too intense on the client's CPU, and having one global timer for ship firing should allow you to have a good number of ships firing away without too much ill effect.

Categories