You could just as well express the same functionality as free functions working on typed data structures, and if you do that, the linker is better at stripping out the parts you don't use.
By the time it gets to the linker, it's exactly the same either way (assuming the equivalent code is written in both styles)...
Yeah there's some counter-examples, such as if you use virtual functions, they'll not be discarded... but this is a straw-man, because the same would occur with free functions if you wrote the equivalent code that manually created a table of pointers to functions (i.e. the function's who's addresses you capture will also no longer be discarded).
I thought of ECS but isn't that a bit overkill or is that the way to go in this scenario? Does anyone know another preferbly OO way of dealing with this problem?
The core of ECS is "composition" -- the core of OO is also "composition" (if you look up "inheritance vs composition" you'll find that OO teaches that you should default to using composition, and only use inheritance where necessary, which is not often). Under both those paradigms, you break your code up into small pieces which each only solve a single problem at a time, and then build complex parts by composing simple parts together.
Many ECS articles compare themselves against the "incorrect" OO styles, usually "deep inheritance" entity hierarchies, where inheritance has been completely over-used, and then offer ECS as a solution to that problem. I've never seen an ECS article that compares ECS against proper composition-based OO though
Do you have any concrete ideas about separating the update and drawing code here?
This is pretty vague, but move all the drawing code out into classes that only do drawing stuff. Don't have game logic and graphics structures intertwined.
You can actually have two completely different worlds/scenes/collections of objects -- one list of game objects, and another list of graphics objects. The update part of your game loop can do stuff to the first list, and the draw part of your game loop can do stuff to the second list. The server can just not create or use the second list. On the client, the game objects obviously need to do stuff to the graphics objects (such as move them around, etc), so the items in the first list can contain pointers into their 'partners' in the second list, but on the server these pointers can just be NULL.