minecraft forge - How would I get a entity and change it's state? - minecraft-forge

I'm starting out with forge modding and I want to make it so all creepers spawn as a charged creeper. I'm using forge MDK 1.16.3.
Here is my event handler:
package com.modder.mod.events;
import net.minecraft.entity.Entity;
import net.minecraftforge.event.entity.living.LivingSpawnEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
public class EventHandler
{
#SubscribeEvent
public static void mobEvent(LivingSpawnEvent event)
{
Entity entity;
}
}
But I do not know what to after this. Thanks in advance!

The LivingSpawnEvent will fire every time an entity is spawned into a world, this includes when you leave and then return so keep that in mind.
As the event fires for every spawning entity, we want to check if the entity is a creeper and so we check that it's an instance of the CreeperEntity class and return if it's not.
if(!(event.getEntity() instanceof CreeperEntity))
return;
Now that we know we have a creeper, we can safely cast it to get the creeper entity. We then check if the creeper is already charged and return if so; as there's no need to continue.
CreeperEntity creeper = (CreeperEntity) event.getEntity();
if(creeper.isCharged())
return;
Normally, creepers are charged by a lightning strike and we can emulate this functionality to make the creeper charged:
LightningBoltEntity lightningBoltEntity = new LightningBoltEntity(event.getWorld().getWorld(), event.getX(), event.getY(), event.getZ(), true);
((ServerWorld) event.getWorld()).addLightningBolt(lightningBoltEntity);
creeper.onStruckByLightning(lightningBoltEntity);
This strike will damage the creeper.
However, if we don't want a lightning strike, we have to change the POWERED data manager boolean. This field is private but we can use reflection.
First, we define a new Field and then try and get the POWERED field and set it to accessible.
Field field;
field = creeper.getClass().getDeclaredField("POWERED");
field.setAccessible(true);
We can then set the value to true which will set the creeper to charged.
DataParameter<Boolean> powered = (DataParameter<Boolean>) field.get(creeper);
creeper.getDataManager().set(powered, true);
Finally, we should wrap this in a try/catch because we don't control the content of CreeperEntity so we can't guarantee that our assumptions about its content will be accurate upon execution, so we define how we will deal with errors. In this case, I'm ignoring any expected exceptions for demonstration purposes.
#SubscribeEvent
public void autoChargeCreeper(LivingSpawnEvent event)
{
if(!(event.getEntity() instanceof CreeperEntity))
return;
CreeperEntity creeper = (CreeperEntity) event.getEntity();
if(creeper.isCharged())
return;
try
{
Field field;
field = creeper.getClass().getDeclaredField("POWERED");
field.setAccessible(true);
DataParameter<Boolean> powered = (DataParameter<Boolean>) field.get(creeper);
creeper.getDataManager().set(powered, true);
} catch (NoSuchFieldException | IllegalAccessException ignored) { }
}

Related

VertX : Check if database config is available

I'm quite new to the Microservice world and particularly vertX. I want my verticle to start anyway even there is no database connection available (e.g. database URL missing in configuration). I already managed to do this and my verticle is starting.
The issue now is that I want my verticle to notice when the database connection is available again and connect to it. How can I do this ?
I thought about creating another Verticle "DatabaseVerticle.java" which would send the current DB config on the event bus and my initial verticle would consume this message and check whether the config info is consistent (reply with success) or still missing some data (reply with fail and make the DatabaseVerticle check again).
This might work (and might not) but does not seem to be the optimal solution for me.
I'd be very glad if someone could suggest a better solution. Thank you !
For your use case, I'd recommend to use the vertx-config. In particular, have a look at the Listening to configuration changes section of the Vert.x Config documentation.
You could create a config retriever and set a handler for changes:
ConfigRetrieverOptions options = new ConfigRetrieverOptions()
.setScanPeriod(2000)
.addStore(myConfigStore);
ConfigRetriever retriever = ConfigRetriever.create(vertx, options);
retriever.getConfig(json -> {
// If DB config available, start the DB client
// Otherwise set a "dbStarted" variable to false
});
retriever.listen(change -> {
// If "dbStarted" is still set to false
// Check the config and start the DB client if possible
// Set "dbStarted" to true when done
});
The ideal way would be some other service telling your service about database connection. Either through event bus or HTTP, what you can do is when someone tries to access your database when connection is not made just try to make some DB call and handle the exception, return a boolean as false. Now when you get a message on event bus, consume it and save it in some config pojo. Now when someone tries to access your database, look for config and if available make a connection.
Your consumer:
public void start(){
EventBus eb = vertx.eventBus();
eb.consumer("database", message -> {
config.setConfig(message.body());
});
}
Your db client(Mongo for this eg):
public class MongoService{
private MongoClient client;
public boolean isAvailable = false;
MongoService(Vertx vertx){
if(config().getString("connection")){
client = MongoClient.createShared(vertx, config().getString("connection"));
isAvailable = true;
}
}
}
Not everything in Vertx should be solved by another verticle.
In this case, you can use .periodic()
http://vertx.io/docs/vertx-core/java/#_don_t_call_us_we_ll_call_you
I assume you have some function that checks the DB for the first time.
Let's call it checkDB()
class PeriodicVerticle extends AbstractVerticle {
private Long timerId;
#Override
public void start() {
System.out.println("Started");
// Should be called each time DB goes offline
final Long timerId = this.vertx.setPeriodic(1000, (l) -> {
final boolean result = checkDB();
// Set some variable telling verticle that DB is back online
if (result) {
cancelTimer();
}
});
setTimerId(timerId);
}
private void cancelTimer() {
System.out.println("Cancelling");
getVertx().cancelTimer(this.timerId);
}
private void setTimerId(final Long timerId) {
this.timerId = timerId;
}
}
Here I play a bit with timerId, since we cannot pass it to cancelTimer() right away. But otherwise, it's quite simple.

SysOperation Framework suppress infolog messages for ReliableAsynchronous but keep them in batch history

I'm just getting my feet wet with the SysOperation framework and I have some ReliableAsynchronous processes that run and call info("starting...") etc.
I want these infolog messages so that when I look in the BatchHistory, I can see them for purposes of investigating later.
But they also launch to the client, from the batch. And I can tell they're from the batch because you can't double click on the infologs to go to the source. Is there someway to either suppress these from popping up on the user's screen and only show in the batch log?
EDIT with some code:
User clicks a button on form action pane that calls an action menu item referencing a class.
In the class, the new method:
public void new()
{
super();
this.parmClassName(classStr(MyControllerClass));
this.parmMethodName(methodStr(MyControllerClass, pickTransLines));
this.parmExecutionMode(SysOperationExecutionMode::ReliableAsynchronous);
// This is meant to be running as a batch task, so don't load syslastvalue
this.parmLoadFromSysLastValue(false);
}
The main method hit from the menu item:
public static void main (Args _args)
{
MyControllerClass controller = new MyControllerClass();
MyContract contract;
WMSOrderTrans wmsOrderTrans;
RefRecId refRecId;
if (_args && _args.dataset() == tableNum(WMSOrderTrans) && _args.record())
{
contract = controller.getDataContractObject();
contract.parmRefRecId(_args.record().RecId);
controller.parmShowDialog(false);
refRecId = controller.doBatch().BatchJobId;
// This creates a batch tracking record
controller.updateCreateTracking(refRecId, _args.record().RecId);
}
}
The controller method that gets launched:
// Main picking method
private void pickTransLines(MyContract_contract)
{
MyTrackingTable tracking;
boolean finished;
BatchHeader batchHeader = BatchHeader::getCurrentBatchHeader();
boolean updateTracking = false;
// NOTE - This infolog launches after a few seconds to the user, but
// you can't double click on the info message to go to the code
// because it's fired from the batch somehow.
info(strFmt("Working on wmsordertrans.recid == %1", _contract.parmRefRecId()));
// Create/Update batch tracker if needed
if (this.isInBatch())
{
// NOTE - This code gets executed so we ARE in batch
this.updateTrackingStuff(...);
}
// Do the pick work
finished = this.doPick(_contract);
if(!finished)
throw error("An error occurred during the picking process.");
}
Then a split second later this launches to my session:
Look at the SysOperationServiceController.afterOperation method,:
[...]
if (_executionMode == SysOperationExecutionMode::ReliableAsynchronous)
{
batch = this.operationReturnValue();
if (batch)
{
infolog.view(Batch::showLog(batch.RecId));
}
}
[...]
This is the code that shows the infolog to the screen for reliable asynchronous processed.
You can create your own controller by extending SysOperationServiceController and use that on your menu item or in code, so do that and overwrite the afterOperation on your new controller, for example like this (didn't test but should work in your case):
if (_executionMode != SysOperationExecutionMode::ReliableAsynchronous)
{
super(_executionMode, _asyncResult);
}

Why my object is not updated in linq?

I have a method where I READ objects from DB, for instance:
public Object getProduct(int categoryId, int productId)
{
DataClassesDataContext db = new DataClassesDataContext(Settings.getDefaultConnectionStringName());
switch (categoryId)
{
case CCategorii.CARTI_ID:
{
IEnumerable<Carti> product = (from c in db.Cartis
where c.Carti_id == productId
&& c.Vizibil == true
select c);
if (product.Count() != 0)
return product.First();
break;
}
//so on
}
}
Now I have another method where I do the update:
public void updateProduct()
{
Object productToBeUpdated = getProduct(1,1);
DataClassesDataContext db = new DataClassesDataContext(Settings.getDefaultConnectionStringName());
//update some properties of the product
productToBeUpdated.setQuantity(productToBeUpdated.getQuantity()+1);
db.submitChanges();
}
Well, the product was succcesfully read from previous method but changes were not done into the DB.
I think the cause is that I do this READ-UPDATE in two different DataContext...If this is the cause how do you threat this situations?
Oh yeah, I can read the product and update in the same method but this means to duplicate the method I use for reading and add to it update stuff... and I would like to avoid this.
I would assume it's because you are using a different context for the read and write. Try moving your DataClassesDataContext variable to class level.
One option is: use a common data context, and pass it to your getXXX methods as a parameter:
public Object getProduct(DataClassesDataContext db, int categoryId, int productId)
{
switch (categoryId)
{
case CCategorii.CARTI_ID:
{
IEnumerable<Carti> product = (from c in db.Cartis
where c.Carti_id == productId
&& c.Vizibil == true
select c);
if (product.Count() != 0)
return product.First();
break;
}
//so on
}
}
and then:
public void updateProduct()
{
using (DataClassesDataContext db = new DataClassesDataContext(Settings.getDefaultConnectionStringName()))
{
Object productToBeUpdated = getProduct(db, 1,1);
//update some properties of the product
productToBeUpdated.setQuantity(productToBeUpdated.getQuantity()+1); // THX #AVD, didn't notice that.
db.submitChanges();
}
}
You are using two different instances of your DataContext.
When implementing a web app, the best option is usually to align the lifetime of your DataContext to the lifetime of one http request. The lifetime you use is just too short.
Another option is to attach the object to the write DataContext:
db.Cartis.Attach(yourReadObject);
updateProperties(yourReadObject);
db.submitChanges();
EDIT
Ok, you have to detach the object from your other context first. See this article on how to do it.
But i really would recommend to use a single DataContext object and extend the lifetime to the httprequest scope.
This can be done really nice with an ioc container like autofac.
You can't use ++ operator and use the same context to update an object. Try this,
productToBeUpdated.setQuantity(productToBeUpdated.getQuantity()+1);
As soon as your DataContext goes out of scope your entity becomes detached from it. That means it's no longer being tracked by your Context and it can't save the changes you make to it.
You could share the context so the entity doesn't get detached from your context or you could reattach it to the second context (DataContext.Attach)

Entity Framework telling me an object is attached when it isn't - why?

I have an object I want to update in the database. I'm new to EF but have done a fair bit of reading. Clearly my approach is wrong, but I don't understand why. FYI the Context referenced throughout is an ObjectContext which is newly instantiated as this code begins and is disposed immediately after. Here is my Update method - the View is the object I want to update in the database and it has 4 ICollection properties whose changes I also wish to save to the database:
public void Update(View view)
{
var original = Read(view.Username, view.ViewId);
original.ViewName = view.ViewName;
ProcessChanges<CostCentre, short>(Context.CostCentres, original.CostCentres, view.CostCentres, "iFinanceEntities.CostCentres", "CostCentreId");
ProcessChanges<LedgerGroup, byte>(Context.LedgerGroups, original.LedgerGroups, view.LedgerGroups, "iFinanceEntities.LedgerGroups", "LedgerGroupId");
ProcessChanges<Division, byte>(Context.Divisions, original.Divisions, view.Divisions, "iFinanceEntities.Divisions", "DivisionId");
ProcessChanges<AnalysisCode, short>(Context.AnalysisCodes, original.AnalysisCodes, view.AnalysisCodes, "iFinanceEntities.AnalysisCodes", "AnalysisCodeId");
int test = Context.SaveChanges();
}
First I get the original from the database because I want to compare its collections with the new set of collections. This should ensure the correct sub-objects are added and removed. I compare each collection in turn using this ProcessChanges method:
private void ProcessChanges<TEntity, TKey>(ObjectSet<TEntity> contextObjects, ICollection<TEntity> originalCollection, ICollection<TEntity> changedCollection, string entitySetName, string pkColumnName)
where TEntity : class, ILookupEntity<TKey>
{
List<TKey> toAdd = changedCollection
.Select(c => c.LookupKey)
.Except(originalCollection.Select(o => o.LookupKey))
.ToList();
List<TKey> toRemove = originalCollection
.Select(o => o.LookupKey)
.Except(changedCollection.Select(c => c.LookupKey))
.ToList();
toAdd.ForEach(a =>
{
var o = changedCollection.Single(c => c.LookupKey.Equals(a));
AttachToOrGet<TEntity, TKey>(entitySetName, pkColumnName, ref o);
originalCollection.Add(o);
});
toRemove.ForEach(r =>
{
var o = originalCollection.Single(c => c.LookupKey.Equals(r));
originalCollection.Remove(o);
});
}
This compares the new collection to the old one and works out which objects to add and which to remove. Note that the collections all contain objects which implement ILookupEntity.
My problems occur on the line where I call AttachToOrGet. This method I got from elsewhere on stackoverflow. I'm using this because I was often getting a message saying that "An object with the same key already exists in the ObjectStateManager" when attaching a new subobject. Hopefully you'll understand my confusion around this when I post the code of this method below:
public void AttachToOrGet<TEntity, TKey>(string entitySetName, string pkColumnName, ref TEntity entity)
where TEntity : class, ILookupEntity<TKey>
{
ObjectStateEntry entry;
// Track whether we need to perform an attach
bool attach = false;
if (Context.ObjectStateManager.TryGetObjectStateEntry(new EntityKey(entitySetName, pkColumnName, entity.LookupKey), out entry))
//if (Context.ObjectStateManager.TryGetObjectStateEntry(Context.CreateEntityKey(entitySetName, entity), out entry))
{
// Re-attach if necessary
attach = entry.State == EntityState.Detached;
// Get the discovered entity to the ref
entity = (TEntity)entry.Entity;
}
else
{
// Attach for the first time
attach = true;
}
if (attach)
Context.AttachTo(entitySetName, entity);
}
Basically this is saying if the entity is not already attached then attach it. But my code is returning false on the Context.ObjectStateManager.TryGetObjectStateEntry line, but throwing an exception on the final line with the message "An object with the same key already exists in the ObjectStateManager". To me this is paradoxical.
As far as I'm concerned I'm trying to achieve something very simple. Something it would take 20 minutes to write a stored procedure for. A simple database update. Frankly I don't care what is attached and what isn't because I don't wish to track changes or create proxies or lazy load or do anything else EF offers me. I just want to take a very simple object and update the database using a minimal number of trips between servers. How is this so complicated? Please someone help me - I've spent a whole day on this!
Update
Here's my ILookupEntity class:
public interface ILookupEntity<TKey>
{
TKey LookupKey { get; }
string DisplayText { get; }
}
Here's how it is implemented in CostCentre:
public partial class CostCentre : IFinancialCode, ILookupEntity<short>
{
#region IFinancialCode Members
public short ID { get { return CostCentreId; } }
public string DisplayText { get { return string.Format("{0} - {1}", Code, Description); } }
#endregion
#region ILookupEntity Members
public short LookupKey
{
get { return ID; }
}
#endregion ILookupEntity Members
}
Well, I've worked through this and found a solution, but I can't say I understand it. The crucial ingredient came when I was performing a check after the comment by #Slauma. I wanted to check I was using the correct entity set name etc so I included the following lines near the top of my AttachToOrGet method:
var key = new EntityKey(entitySetName, pkColumnName, entity.LookupKey);
object temp;
if (!Context.TryGetObjectByKey(key, out temp))
throw new Exception(string.Format("No entity was found in {0} with key {1}", entitySetName, entity.LookupKey));
Bizarrely this alone resolved the problem. For some reason, once I'd called the TryGetObjectByKey then the ObjectStateManager.TryGetObjectStateEntry call actually started locating the attached entity. Miraculous. I'd love it if anyone can explain this.
By the way, I also needed to include the following code, but that's just because in my case the modelled entities are located in a separate assembly from the context itself.
Assembly assembly = typeof(CostCentre).Assembly;
Context.MetadataWorkspace.LoadFromAssembly(assembly);

ASP.NET Cache and File Dependancies

I want a ASP.NET cache item to be recycled when a specific file is touched, but the following code is not working:
HttpContext.Current.Cache.Insert(
"Key",
SomeObject,
new CacheDependency(Server.MapPath("SomeFile.txt")),
DateTime.MaxValue,
TimeSpan.Zero,
CacheItemPriority.High,
null);
"SomeFile.txt" does not seem to be checked when I'm hitting the cache, and modifying it does not cause this item to be invalidated.
What am I doing wrong?
Problem Solved:
This was a unique and interesting problem, so I'm going to document the cause and solution here as an Answer, for future searchers.
Something I left out in my question was that this cache insertion was happening in a service class implementing the singleton pattern.
In a nutshell:
public class Service
{
private static readonly Service _Instance = new Service();
static Service () { }
private Service () { }
public static Service Instance
{
get { return _Instance; }
}
// The expensive data that this service exposes
private someObject _data = null;
public someObject Data
{
get
{
if (_data == null)
loadData();
return _data;
}
}
private void loadData()
{
_data = GetFromCache();
if (_data == null)
{
// Get the data from our datasource
_data = ExpensiveDataSourceGet();
// Insert into Cache
HttpContext.Current.Cache.Insert(etc);
}
}
}
It may be obvious to some, but the culprit here is lazy loading within the singleton pattern. I was so caught up thinking that the cache wasn't being invalidated, that I forgot that the state of the singleton would be persisted for as long as the worker process was alive.
Cache.Insert has an overload that allows you to specify a event handler for when the cache item is removed, my first test was to create a dummy handler and set a breakpoint within it. Once I saw that the cache was being cleared, I realized that "_data" was not being reset to null, so the next request to the singleton loaded the lazy loaded value.
In a sense, I was double caching, though the singleton cache was very short lived, but long enough to be annoying.
The solution?
HttpContext.Current.Cache.Insert(
"Key",
SomeObject,
new CacheDependency(Server.MapPath("SomeFile.txt")),
DateTime.MaxValue,
TimeSpan.Zero,
CacheItemPriority.High,
delegate(string key, object value, CacheItemRemovedReason reason)
{
_data = null;
}
);
When the cache is cleared, the state within the singleton must also be cleared...problem solved.
Lesson learned here? Don't put state in a singleton.
Is ASP.NET running under an account with the proper permissions for the file specified in the CacheDependency? If not, then this might be one reason why the CacheDependency is not working properly.
I think you'll need to specify a path:
var d = new CacheDependency(Server.MapPath("SomeFile.txt"));
Prepend with ~\App_Data as needed.
Your code looks fine to me. However, beyond this snippet, anything could be going on.
Are you re-inserting on every postback by any chance?
Try making your cache dependency a class field, and checking it on every postback. Modify the file in between and see if it ever registers as "Changed". e.g.:
public partial class _Default : System.Web.UI.Page
{
CacheDependency dep;
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
dep = new CacheDependency(Server.MapPath("SomeFile.txt"));
HttpContext.Current.Cache.Insert(
"Key",
new Object(),
dep,
DateTime.MaxValue,
TimeSpan.Zero, CacheItemPriority.High, null);
}
if (dep.HasChanged)
Response.Write("changed!");
else
Response.Write("no change :("); }}
The only way I am able to reproduce this behavior is if the path provided to the constructor of CacheDependency does not exist. The CacheDependency will not throw an exception if the path doesn't exist, so it can be a little misleading.

Resources