The approach and some evolutions / pitfalls
Fairly standard. To give your approach a handle of sorts: you want to centralise most or all interactions to go through some high level (if not top level) controller, say
Environment, which reduces coupling. We'll call it something like "top level interactions mediator / intermediary / proxy". See associated design patterns.
The problem comes in when that top level, generalised controller
Environment becomes too cluttered, which will usually happen quickly in such a general class.
This is where you begin factoring
Environment into new subsystems, delegating its responsibilities further down the control tree or hierarchy.
For example, if trading is a possiblity, then
Environment, which may receive a message from a player requesting to trade, may delegate these messages down to a
TradingSystem which acts as intermediary for the exchange of items between the two entities in question, which could be actual trading, looting a corpse, or pickpocketing, depending on the parameters in that message; this would probably be reflected in a similar UI between the three styles of interaction.
TradingSystem may fire a message when that UI closes, indicating that the interaction is complete, which is picked up by
Environment so that it can, for example, ensure that corpse can't be looted again, or even remove the body altogether once looted.
TradingSystem can delegate control down, even further.
What we typically don't want is
TradingSystem having a reference back up to its controller
Environment. This increases coupling, and can make it difficult to trace what is controlling what, since subordinate controllers can (via method call) directly cause a parent controller to react, rather than requesting an action on part of a parent by notifying them via event / message / signal - you suggested
choose to raise or player entities can choose to respond to an event - especially true for higher level controllers which should never have to listen to subordinates.
EDIT re question about messaging coupling The Observer Pattern, AKA pub-sub, loosens coupling in the following way:
Publisher will have a list of
Subscribers to be notified. These references are "loose" because they refer to type
Subscriber which comes from a generic list of
Subscribers which the
Publisher thus has no idea what the actual, concrete types (such as
Environment) these objects are, so it cannot call public methods on them, meddle with their public members, etc. And because the
Subscriber is intrinsically limited in functionality, it should remain the same throughout development. So no need to worry about these
Subscribers changing and breaking
Will this be overkill?
Not really, but...
I'd always take the simplest approach for now, until that starts to get in the way of efficient development. Then I'd start formulating a plan as to how to solve the problem, systematically. As this plan is crystallising in my mind, and assuming the project is not on a schedule e.g. for a client where other tasks take priority, I'd tie up loose ends on current tasks, merge into the base branch, create a new branch off this base, and implement the planned solution in full.
I would not try to preempt what those problems might be as that is nearly always a waste of time and effort. It may be better to get started on making your game and do the simplest thing that works (for example, direct method calls or a modifying a globally-accessible array of message slots you can read and write as and when), for the time being.
It's good to stay on top of things, and working in "difficult" code can be tedious, but sometimes it is worth the wait to see all the potential ways in which you have problems with a given (sub)system, and the ways in which you could solve them all at once, so that when you finally do fix them, you do so as effectively and universally as possible for the current stage of development.
In the meantime, do the simplest thing that works.